[Spring] Reactor Netty 동작 원리를 알아보자

Hocaron·2025년 3월 14일
1

Spring

목록 보기
47/47

Reactor Netty는 비동기 논블로킹 이벤트 루프 기반의 네트워크 프레임워크로, 하나의 스레드에서 I/O 작업을 논블로킹으로 처리할 수 있다. 어떻게 하나의 스레드로 수많은 I/O 작업을 논블로킹으로 처리할 수 있는걸까?
Netty의 동작 방식과 Tomcat NIO와의 차이점을 비교하면서 동작원리를 살펴보자.

1. Reactor Netty의 기본 원리

Reactor Netty는 Netty 기반의 논블로킹 I/O 서버이며, 이벤트 루프(Event Loop) 모델을 활용하여 싱글 스레드에서도 다중 연결을 처리할 수 있도록 설계되었다.

  • Reactor 패턴을 기반으로 동작 (이벤트 루프 + 논블로킹 I/O)
  • Epoll/Kqueue를 활용한 다중 이벤트 처리 (리눅스 기반)
  • 하나의 스레드에서 여러 네트워크 연결을 논블로킹으로 관리
  • Future / Promise 기반 비동기 처리 (콜백 + 이벤트 기반)
  • Spring WebFlux, Spring Cloud Gateway 비동기 네트워크 서비스의 기본

1.2. Reactor Netty가 하나의 스레드에서 논블로킹으로 동작할 수 있는 이유

하나의 스레드가 여러 네트워크 요청을 처리할 수 있는 이유는 이벤트 루프 + 논블로킹 I/O 모델 때문이다.

논블로킹 I/O (NIO) 기반 이벤트 루프 모델

  • Reactor Netty는 OS 커널의 I/O 멀티플렉싱 기능(epoll, kqueue 등)을 활용하여 하나의 스레드에서 여러 I/O 작업을 감지할 수 있음.
  • 데이터가 준비되지 않은 상태에서 블로킹 없이 다른 요청을 처리할 수 있음.
  • 네트워크 I/O가 완료되었을 때만 이벤트 루프가 트리거되므로, 불필요한 CPU 낭비가 없음.

기존 블로킹 모델(Tomcat BIO)과의 차이점

  • 블로킹 I/O (BIO) 기반에서는 각 요청마다 별도의 스레드가 필요했음.
  • 하지만, Reactor Netty에서는 하나의 EventLoop가 다수의 요청을 관리할 수 있음.
  • 즉, I/O가 준비될 때까지 기다리는 것이 아니라, 준비된 이벤트만 처리하는 방식이므로 높은 성능을 유지할 수 있음.

1.3 Tomcat NIO와 Reactor Netty 비교

Tomcat NIO

1. 클라이언트 요청 도착
   +------------------------+
   |   Client Request       |
   | (TCP Connection)       |
   +------------------------+
               |
               v
2. Tomcat Acceptor Thread에서 요청 감지
   +-----------------------------+
   | Tomcat Acceptor Thread      |
   |  - Accepts TCP Connection   |
   |  - Assigns Worker Thread    |
   +-----------------------------+
               |
               v
3. NIO Selector가 소켓을 감시
   +---------------------------+
   |   Java NIO Selector       |
   |  - Uses select()/poll()   |
   +---------------------------+
               |
               v
4. 데이터가 준비되면 Worker Thread에서 처리
   +------------------------+
   |   Worker Thread        |
   | - Reads Request Data   |
   +------------------------+
               |
               v
5. 서블릿에서 비즈니스 로직 처리
   +------------------------+
   |   Servlet Container    |
   | - Executes Business Logic |
   +------------------------+
               |
               v
6. Worker Thread에서 응답 생성 후 전송
   +------------------------+
   |   Worker Thread        |
   | - Sends Response       |
   +------------------------+
               |
               v
7. 클라이언트 응답 수신
   +------------------------+
   |   Client Receives Data  |
   +------------------------+
  • Tomcat NIO도 논블로킹이지만, 요청마다 Worker Thread를 할당해야 하므로 스레드 풀의 크기에 따라 성능이 제한될 수 있음.

Reactor Netty

1. 클라이언트 요청 도착
   +------------------------+
   |   Client Request       |
   | (TCP Connection)       |
   +------------------------+
               |
               v
2. Reactor Netty의 Event Loop에서 요청 감지 (Selector 등록)
   +------------------------+
   | Event Loop (I/O Thread)|
   | - Registers socket FD  |
   | - Non-blocking I/O     |
   | - Uses epoll/kqueue    |
   +------------------------+
               |
               v
3. OS 커널에서 epoll/kqueue에 소켓 등록 (I/O 대기)
   +--------------------------+
   | OS Kernel (epoll/kqueue) |
   |  - Watches socket events |
   |  - No CPU block          |
   +--------------------------+
               |
               v
4. 데이터가 준비되면 커널에서 이벤트 발생
   +--------------------------+
   | OS Kernel Event Trigger  |
   |  - I/O Ready             |
   |  - Notifies Event Loop   |
   +--------------------------+
               |
               v
5. 이벤트 루프가 큐에 작업 추가 (Task Queue)
   +--------------------------+
   | Event Loop Task Queue    |
   |  - Stores I/O Events     |
   |  - Processes Tasks Async |
   +--------------------------+
               |
               v
6. 이벤트 루프가 Task Queue에서 이벤트를 꺼내어 실행
   +----------------------------+
   |  Reactor Netty Pipeline    |
   |  - Decodes Request         |
   |  - Business Logic Execution |
   |  - Encodes Response        |
   +----------------------------+
               |
               v
7. OS를 통해 응답 전송 (비동기)
   +--------------------------+
   | OS Kernel (epoll/kqueue) |
   |  - Send Response         |
   +--------------------------+
               |
               v
8. 클라이언트 응답 수신
   +------------------------+
   |   Client Receives Data |
   +------------------------+
  • Reactor Netty는 하나의 EventLoop에서 논블로킹으로 여러 요청을 처리할 수 있으므로, 스레드 오버헤드가 적고 성능이 뛰어남.

2. OS 커널에서 이벤트를 발생시키는 과정

Reactor Netty가 사용하는 논블로킹 I/O 모델은 OS 커널의 I/O 멀티플렉싱 기법(epoll, kqueue, select)을 활용한다.
이 방식을 통해 I/O 작업이 준비될 때만 이벤트를 발생시키며, CPU가 대기하는 것을 방지한다.

  1. Reactor Netty가 OS 커널의 epoll/kqueue에 소켓(FD) 등록
  2. OS 커널이 소켓의 I/O 상태를 감시
  3. 소켓이 읽기/쓰기 가능할 때 OS가 이벤트를 생성
  4. Reactor Netty의 이벤트 루프가 이 이벤트를 감지하여 작업 처리

2.1 epoll vs kqueue vs select 차이점 정리

OS에서 네트워크 I/O 이벤트를 감지하는 방법에는 여러 가지가 있으며, 대표적으로 select, poll, epoll(Linux), kqueue(BSD/macOS) 등이 있다.

기법지원 OS파일 디스크립터(FD) 제한작동 방식성능
select모든 OSFD_SETSIZE 제한 (1024)모든 FD를 검사 (O(N))느림 (O(N))
poll모든 OS제한 없음전체 FD를 순차 검색 (O(N))느림 (O(N))
epollLinux제한 없음이벤트 기반 감지 (O(1))빠름 (O(1))
kqueueBSD/macOS제한 없음이벤트 기반 감지 (O(1))빠름 (O(1))
  • Reactor Netty는 성능을 최적화하기 위해 epoll(Linux) 또는 kqueue(macOS)를 자동으로 선택하여 사용한다.

select: 가장 오래된 방식

  • OS에 파일 디스크립터(FD) 목록을 전달하고, 모든 FD를 검사하여 I/O 이벤트가 있는지 확인한다.
  • 모든 FD를 검사하므로 FD 개수가 많아지면 성능이 급격히 저하된다. (O(N) 복잡도)
  • 하나의 fd_set 구조체를 사용하여 FD 개수가 보통 1024개로 제한됨 (FD_SETSIZE 제한)

select 동작 방식

  1. FD 목록을 OS 커널에 전달
  2. OS 커널이 모든 FD를 검사 (O(N) 시간복잡도)
  3. 이벤트가 발생한 FD를 반환
  4. 사용자 코드가 이벤트를 처리

poll: select 개선 버전

  • select와 유사하지만 FD 개수 제한이 없다.
  • 하지만 여전히 모든 FD를 순차적으로 검사해야 하므로 O(N) 복잡도를 가짐.
  • select보다 개선되었지만 여전히 성능이 좋지 않다.

poll 동작 방식

  1. FD 목록을 OS 커널에 전달 (배열 기반)
  2. OS 커널이 모든 FD를 검사 (O(N) 시간복잡도)
  3. 이벤트가 발생한 FD를 반환
  4. 사용자 코드가 이벤트를 처리

epoll: select/poll의 개선판 (Linux)

  • 이벤트 기반 비동기 감지 방식으로 동작 (O(1) 복잡도)
  • FD 개수 제한이 없음
  • 이벤트가 발생한 FD만 반환하므로 불필요한 반복을 줄임
  • Reactor Netty에서 기본적으로 Linux 환경에서 사용됨

epoll 동작 방식

  1. epoll_create()를 호출하여 epoll 인스턴스를 생성
  2. epoll_ctl()을 통해 감시할 FD를 등록 (O(1)) -> FD를 red-black tree에 저장하여 추가/삭제 연산을 O(log N)으로 처리
  3. epoll_wait()을 호출하여 이벤트가 발생한 FD만 반환 (O(1)) -> 이벤트가 발생한 FD는 ready list에 따로 저장
  4. 사용자 코드가 이벤트를 처리

epoll이 빠른 이유

epoll은 파일 디스크립터(FD)를 red-black tree에 저장하고, 이벤트가 발생하면 ready list에서 즉시 가져올 수 있도록 설계되어 있다.

  • red-black tree (O(log N))
    • epoll_ctl()을 호출하면 FD를 red-black tree에 저장하여 추가/삭제 연산을 O(log N)으로 처리한다.
    • FD 개수가 많아도 빠르게 관리 가능.
  • ready list (O(1))
    • 이벤트가 발생한 FD는 ready list에 따로 저장되며,
    • epoll_wait()이 호출될 때 즉시 O(1)로 조회 가능하다.

kqueue: epoll과 유사한 BSD/macOS 방식

  • BSD 및 macOS에서 사용되는 이벤트 기반 비동기 감지 방식
  • epoll과 거의 동일한 원리로 동작 (O(1) 복잡도)
  • Reactor Netty는 macOS/BSD 환경에서 자동으로 kqueue를 사용

kqueue 동작 방식

  1. kqueue()를 호출하여 kqueue 인스턴스를 생성
  2. kevent()를 통해 감시할 FD를 등록 (O(1))
  3. kevent()를 호출하여 이벤트가 발생한 FD만 반환 (O(1))
  4. 사용자 코드가 이벤트를 처리
profile
기록을 통한 성장을

0개의 댓글

관련 채용 정보