HTTP 200 OK

Memento mori & Carpe diem

Spring

undertow VS tomcat 비동기 처리(Feat.이벤트루프란?)

sjoongh 2024. 11. 9. 21:36

Undertow와 Tomcat의 비동기 처리

Undertow Tomcat은 자바 기반 웹 서버인건 동일하지만 비동기 I/O 처리하는 방식은 다릅니다. 이 둘에 대한 차이점과 어떠한 원리로 동작 하는지 살펴보겠습니다. 설명을 드리기에 앞서 undertow에서 비동기 프로그래밍을 처리할때 중요한 개념인 이벤트 루프에 대해 소개하겠습니다.

 

 

이벤트 루프(Event Loop)란?

이벤트 루프는 비동기 프로그래밍 모델에서 중요한 역할을 하는 개념으로 싱글 스레드 환경에서 비동기 처리를 지원하기 위해 사용됩니다. 이벤트를 큐에 넣고 큐에 있는 이벤트를 하나씩 처리하는 방식(콜스택&큐)으로 동작합니다. 이러한 이벤트 루프의 순환 과정을 통해 실시간으로 이벤트를 처리하며 동시성을 관리하고 효율적으로 자원을 사용할 수 있습니다.

 

 

 

 

Undertow의 이벤트 루프

Undertow는 넷티(Netty)와 같은 비동기 I/O를 사용하고 있어 동일한 방식으로 이벤트 루프를 구현합니다. 하지만 Undertow는 필요에 따라 동기 방식으로 전환할 수 있어 Netty보다 유연하게 사용할 수 있습니다. 앞선 이유때문에 현업에서도 유용하게 사용하고 있습니다. Undertow의 주요 특징은 다음과 같습니다

  • 비동기 I/O: Undertow는 비동기 I/O 모델을 사용하여 각 연결에 대한 요청과 응답을 비동기적으로 처리합니다. 이를 통해 블로킹 I/O의 문제를 피하고, 높은 동시성을 제공합니다.
  • 멀티스레드 이벤트 루프: Undertow는 여러 개의 이벤트 루프 스레드를 사용하여 병렬로 작업을 처리합니다. 각 스레드는 큐에 있는 작업을 하나씩 처리하여 높은 처리량을 유지합니다.
  • 커스터마이징 가능: Undertow는 사용자가 직접 이벤트 루프의 동작을 커스터마이징할 수 있도록 다양한 설정을 제공합니다.

 

Tomcat의 비동기처리

Tomcat은 NIO (Non-blocking I/O) 커넥터를 통해 비동기 I/O를 지원합니다. Tomcat의 주요 특징은 다음과 같습니다:

  • NIO 커넥터: Tomcat의 NIO 커넥터는 자바의 NIO 패키지를 사용하여 비동기 I/O를 처리합니다. 이를 통해 블로킹 I/O의 문제를 피하고, 많은 수의 동시 연결을 처리할 수 있습니다.
  • 워크 스레드: Tomcat은 워크 스레드 풀을 사용하여 요청을 처리합니다. 각 스레드는 요청을 큐에서 가져와 처리하고, 처리 완료 후 다음 요청을 처리합니다.
  • NIO2와 APR: Tomcat은 NIO2와 APR(Apache Portable Runtime) 커넥터를 통해 더 높은 성능과 확장성을 제공합니다. 특히 APR 커넥터는 네이티브 라이브러리를 사용하여 성능을 극대화합니다.

 

Undertow 의 이벤트 루프 동작

Undertow는 Netty의 확장 버전이므로 Netty를 기준으로 설명드리겠습니다. Netty의 이벤트 루프 모델은 다음과 같은 특징을 가지고 있습니다.

  1. 이벤트 루프 그룹:
    • Netty는 EventLoopGroup을 사용하여 이벤트 루프를 관리합니다. 이벤트 루프 그룹은 여러 이벤트 루프 스레드를 포함하며, 각 스레드는 이벤트를 처리하는 역할을 합니다.
    • Netty의 이벤트 루프는 기본적으로 싱글스레드 모델을 따릅니다. 각 EventLoop는 하나의 스레드에서 실행되며, 여러 Channel(네트워크 연결)을 처리할 수 있습니다.
    • Netty는 일반적으로 두 가지 유형의 이벤트 루프 그룹을 사용합니다:
      • Boss Group: 클라이언트 연결 요청을 처리하고, 연결을 수락하는 역할을 합니다.
      • Worker Group: 실제 I/O 작업을 처리합니다. 각 연결에 대해 하나 이상의 이벤트 루프 스레드가 할당됩니다.
  2. 채널과 파이프라인:
    • Netty의 Channel은 네트워크 연결을 나타내며, 각 채널은 하나의 이벤트 루프 스레드에 바인딩됩니다.
    • 각 Channel은 하나의 EventLoop에 바인딩됩니다. 이는 같은 EventLoop에 바인딩된 모든 Channel이 동일한 스레드에서 처리된다는 것을 의미합니다. 따라서 스레드 간의 컨텍스트 스위칭을 줄이고, 동기화 비용을 최소화할 수 있습니다.
    • ChannelPipeline은 채널에 연결된 이벤트 처리 핸들러 체인입니다. 이벤트가 발생하면 파이프라인을 통해 순차적으로 핸들러가 호출됩니다.
  3. 이벤트 처리:
    • Netty는 이벤트를 비동기적으로 처리합니다. 이벤트는 이벤트 루프 스레드에 의해 큐에 추가되고, 스레드는 큐에서 이벤트를 하나씩 꺼내어 처리합니다.
    • 이벤트는 다양한 종류가 있습니다. 채널 등록, 데이터 읽기/쓰기, 예외 처리 등을 수행합니다.

 

이벤트 루프 동작

  • Netty의 이벤트 루프는 기본적으로 싱글스레드 모델을 따릅니다. 각 EventLoop는 하나의 스레드에서 실행되며, 여러 Channel(네트워크 연결)을 처리할 수 있습니다.
  • 각 Channel은 하나의 EventLoop에 바인딩됩니다. 이는 같은 EventLoop에 바인딩된 모든 Channel이 동일한 스레드에서 처리된다는 것을 의미합니다. 따라서 스레드 간의 컨텍스트 스위칭을 줄이고, 동기화 비용을 최소화할 수 있습니다.

 

멀티스레드 이벤트 루프 그룹

  • EventLoopGroup은 여러 개의 EventLoop(각각 싱글스레드)를 포함하는 스레드 풀입니다. 이를 통해 Netty는 멀티스레드 환경에서도 효율적으로 동작할 수 있습니다.
  • 클라이언트 연결 요청은 Boss Group의 이벤트 루프에 의해 수락되고, 각 연결은 Worker Group의 이벤트 루프에 할당됩니다. 이렇게 하면 여러 스레드에서 동시에 I/O 작업을 처리할 수 있습니다.
  • 이벤트 루프 동작:
    • 싱글스레드 이벤트 루프: 각 EventLoop는 단일 스레드에서 실행되며, 여러 Channel을 처리할 수 있습니다.
    • 멀티스레드 환경: EventLoopGroup은 여러 EventLoop를 포함할 수 있어, 멀티스레드 환경에서도 동작 가능합니다. 각 EventLoop는 단일 스레드로 실행되지만, 여러 EventLoop가 동시에 실행되어 여러 스레드를 사용할 수 있습니다.
  • 작업 분배:
    • Boss Group에서 클라이언트 연결 요청을 수락하고, 각 연결을 Worker Group의 EventLoop에 할당합니다.
    • 각 EventLoop는 할당된 Channel에서 발생하는 I/O 이벤트를 비동기적으로 처리합니다.

 

 

Netty 동작 순서

  • 이벤트 등록: 클라이언트 연결이 발생하면, Boss Group의 EventLoop가 이를 감지하고 새로운 Channel을 생성합니다.
  • 채널 등록: 생성된 Channel은 Worker Group의 EventLoop에 등록됩니다.
  • 이벤트 큐에 추가: 각 Channel에서 발생하는 I/O 이벤트는 해당 EventLoop의 이벤트 큐에 추가됩니다.
  • 이벤트 처리: EventLoop는 큐에서 이벤트를 하나씩 꺼내어 처리하며 비동기적으로 I/O작업 및 비즈니스 로직 실행 등이 포함됩니다.
  • 콜백 실행: 처리 완료 후, 콜백 함수를 호출하여 결과를 반환하거나 후속 작업을 실행합니다.
  • 반복: 이벤트 루프는 큐가 비어 있지 않은 한 계속해서 이벤트를 처리합니다.
  •  

 

Tomcat 서버 동작

 

Tomcat 비동기 I/O를 사용하여 서블릿 요청을 처리할 수 있습니다. Tomcat의 비동기 처리 방식은 Undertow와는 약간 다릅니다.

  1. 커넥터와 프로세서:
    • Tomcat의 NIO 커넥터는 비동기 I/O를 처리하기 위해 NIO 채널을 사용합니다. 커넥터는 클라이언트 연결을 수락하고, 요청을 프로세서에게 전달합니다.
    • Acceptor: 클라이언트 연결을 수락하는 역할을 합니다.
    • Poller: 비동기 I/O 이벤트를 감지하고, 해당 이벤트를 처리하기 위해 워커 스레드에 할당합니다.
  2. 워크 스레드 풀:
    • Tomcat은 워크 스레드 풀을 사용하여 요청을 처리합니다. 요청이 들어오면, Poller는 워크 스레드 풀에서 사용 가능한 스레드를 가져와 요청을 처리합니다.
    • 워크 스레드는 요청을 처리하고 응답을 생성한 후, 다시 스레드 풀로 반환됩니다.
  3. 서블릿 비동기 처리:
    • Tomcat은 서블릿 3.0에서 도입된 비동기 서블릿 API를 지원합니다. 이를 통해 서블릿이 비동기적으로 요청을 처리할 수 있습니다.
    • 비동기 요청이 시작되면, 서블릿은 요청 처리를 다른 스레드로 넘기고, 메인 스레드는 즉시 반환됩니다. 요청 처리가 완료되면, 응답을 클라이언트에게 전송합니다.

 

Tomcat 동작 순서

  • 연결 수락: Acceptor 스레드가 클라이언트 연결을 수락합니다.
  • 이벤트 감지: Poller가 NIO 셀렉터를 사용하여 I/O 이벤트를 감지합니다.
  • 이벤트 할당: 감지된 이벤트는 워크 스레드 풀에서 사용 가능한 스레드에 할당됩니다.
  • 요청 처리: 워크 스레드는 할당된 요청을 처리하고, 처리 완료 후 스레드 풀로 반환됩니다.

 

스레드풀 설정

  • Undertow : EventLoopGroup의 설정은 Worker Thread의 수와 동작에 영향을 미칩니다.
  • Tomcat: 각 워크 스레드는 스레드풀 설정에 따라 독립적으로 동작

 

메모리 공유

  • Undertow : 각 요청은 Channel과 연관되어 있으며 공통 메모리를 안전하게 공유할 수 있도록 설계됨
  • Tomcat: 모든 워크 스레드가 JVM 메모리를 공유하여 객체나 데이터를 공유할 수 있지만 동기화 필요

 

결론

  • Undertow: Netty와 같은 비동기 I/O 라이브러리를 사용하고 소수의 스레드로 이벤트 루프에서 모든 요청을 비동기 처리하기에 자원 사용이 적고 동시성에 유리합니다.
  • Tomcat: tomcat 또한 비동기 처리 방식을 구현할 수 있고 NIO 커넥터를 통해 비동기 I/O를 처리하며, NIO2와 APR 커넥터를 사용하여 더 높은 성능을 제공할 수 있습니다. 하지만 본질적으로는 동기 요청처리에 적합하며 비동기 작업에서는 Undertow에 비해 유연성이 낮습니다.

 

출처

https://www.programmersought.com/article/1699692284/#google_vignette

https://livebook.manning.com/book/netty-in-action/chapter-7/24