Netty & EventLoop

Spring WebFlux는 기본적으로 내장 서버로 Netty를 사용한다.

  • Netty는 고성능 네트워크 애플리케이션을 개발하기 위한 비동기 이벤트 기반 프레임워크

  • WebFlux가 지향하는 논블로킹(Non-Blocking) I/O 모델을 가장 효율적으로 구현하는 핵심 기술

Netty

  • 네트워크 프레임워크: TCP/UDP 소켓과 같은 저수준(low-level) 네트워크 프로그래밍을 추상화하여 개발자가 비즈니스 로직에 집중 가능

  • 이벤트 기반 및 비동기: 모든 I/O 작업(연결 수립, 데이터 수신 등)을 이벤트로 간주

    • 작업이 완료되면 콜백을 통해 결과를 처리하는 방식으로 동작

    • 이로 인해 작업이 진행되는 동안 스레드 차단 없이 다른 작업 수행 가능

적은 수의 스레드로 수많은 동시 연결을 효율적으로 처리할 수 있는 Netty의 특성은 WebFlux의 리액티브 모델과 완벽하게 부합하여 내장 서버로 채택되었다.

이벤트 루프 기반의 비동기 동작 원리

Netty의 핵심은 이벤트 루프(Event Loop) 모델로, 성능의 핵심 역할을 한다.

  • 이벤트 루프(Event Loop)

    • 무한 루프를 돌면서 자신에게 할당된 채널(Channel, 클라이언트와의 연결)에서 발생하는 이벤트를 감지하고 처리하는 스레드 할당

    • Netty 서버는 보통 CPU 코어 수에 맞춰 소수의 이벤트 루프 스레드를 생성하여 사용

    • 하나의 이벤트 루프 스레드는 하나 이상의 채널을 담당하며, 해당 채널들에서 발생하는 모든 이벤트를 순차적으로 처리

  • 동작 과정

    1. 이벤트 루프는 자신에게 할당된 채널들을 계속해서 확인하며 이벤트 발생을 감시

    2. 이벤트가 발생하면(예: 클라이언트로부터 데이터 수신), 이벤트 큐(Task Queue)에 해당 작업을 등록

    3. 이벤트 루프는 큐에서 작업을 하나씩 꺼내 등록된 핸들러(Handler, 개발자가 작성한 로직)를 실행

    4. 핸들러의 실행은 매우 짧은 시간 안에 끝나야 하며, 절대 블로킹(Blocking) 작업을 포함해서는 안 됨

    5. 작업 처리가 끝나면 이벤트 루프는 다시 채널들을 감시하는 상태로 복귀

  • 핵심 원칙

    • 하나의 이벤트 루프 스레드가 수많은 연결을 동시에 처리하기 때문에, 만약 하나의 작업에서 스레드가 멈추면(block) 해당 스레드에 할당된 다른 모든 연결의 작업 처리도 블로킹

    • 때문에 블로킹 코드를 발견했을 때 subscribeOn(Schedulers.boundedElastic())을 사용하여 작업을 이벤트 루프 스레드가 아닌 다른 스레드로 분리하는 것이 중요

Netty의 스레드 구조

실제 Netty는 스레드별로 역할을 분리하여 효율성을 극대화한다.

  • Boss 그룹

    • 보통 단일 스레드로 구성

    • 오직 서버 포트를 바인딩하고 클라이언트의 새로운 연결 요청을 수락(accept)하는 역할만 담당

    • 새로운 연결이 수립되면, 해당 연결(채널)을 Worker 그룹의 이벤트 루프 중 하나에 등록하고 자신은 즉시 다음 연결을 받기 위해 대기

  • Worker 그룹

    • CPU 코어 수에 맞춰 생성된 여러 개의 이벤트 루프 스레드로 구성

    • Boss 그룹으로부터 넘겨받은 채널에서 발생하는 모든 I/O 이벤트(데이터 읽기, 쓰기 등) 처리

    • 실질적인 데이터 처리와 비즈니스 로직이 실행하는 스레드

이러한 구조 덕분에 연결 수락과 데이터 처리가 분리되어, 각자의 역할에만 집중함으로써 시스템 전체의 성능을 높일 수 있다.

리액티브 모델의 병목 지점과 과부하 관리

논블로킹 모델은 블로킹 모델의 스레드 고갈 문제를 해결하지만, 병목 지점이 스레드 풀의 크기에서 CPU와 메모리와 같은 다른 시스템 자원으로 전환될 뿐이다.

병목 패러다임의 전환

  • 블로킹 모델 (Thread-per-Request)

    • 한계점: 동시 요청 수가 스레드 풀의 가용 스레드 수에 의해 물리적으로 제한

    • 병목 현상

      • I/O 대기 상태의 스레드가 증가하면서 자원(특히 메모리)은 소모되지만, 실제 작업은 처리되지 않음

      • 가용 스레드가 고갈되면 새로운 요청은 처리가 거부되거나 장시간 대기

  • 논블로킹 모델 (Event Loop)

    • 한계점: 시스템의 CPU 연산 능력과 가용 메모리가 한계점으로 작용

    • 병목 현상

      • CPU 부하: 시스템의 처리 용량을 초과하는 요청이 유입되면, 이벤트 루프 스레드의 CPU 사용률이 증가하면서 처리 시간이 전반적으로 증가

      • 메모리 문제: 처리 속도보다 유입 속도가 빠를 경우, 처리 대기 중인 요청 데이터가 메모리에 지속적으로 누적되면서 OutOfMemoryError를 유발

과부하 관리 메커니즘

Spring WebFlux의 근간을 이루는 Netty는 과부하로부터 시스템을 보호하기 위해 다층적인 방어 메커니즘을 갖추고 있다.

  • Backpressure(배압)

    • 리액티브 스트림의 핵심 원칙으로, 소비자가 자신의 처리 용량에 맞춰 생산자의 데이터 생산 속도를 제어하는 피드백 메커니즘

    • 네트워크 레벨에서는 TCP 흐름 제어가 이 역할을 자연스럽게 수행

      • 애플리케이션이 TCP 수신 버퍼에서 데이터를 소비하는 속도가 느려지면, 버퍼가 채워지고 TCP 윈도우(Window) 크기가 감소

      • 이는 송신 측(클라이언트)의 전송 속도를 감소시켜 시스템의 처리 속도와 유입 속도를 동기화

Last updated

Was this helpful?