본문 바로가기
Server/Spring

Spring WebFlux 와 Netty

by min_gui 2025. 5. 12.

 

현업 프로젝트에서는 R2DBC, WebFlux 기술 스택으로 새롭게 개발이 진행 됐습니다.
WebFlux에 대해서 알아 보려구 합니다.

 

기존 프로젝트는 Spring MVC + JDBC 조합은 Blocking 방식이고, 이와 다르게 이번에는
WebFlux + R2DBC 조합으로 Non-Blocking 방식으로 진행하게 되었습니다.

 

WebFlux(Non-Blocking) + JDBC(Blocking) 조합은 권장하지 않습니다. WebFlux의 Non-Blocking 장점 이 사라집니다.

그리고 WebFlux는 적은수의 스레드로 많은 요청을 처리하게 되어 있는데, JDBC를 사용하면 DB 호출 마다 스레드가 Blocking 되어 리소스가 빠르게 고갈 됩니다.

이렇게 되면 WebFlux가 빠르더라도 JDBC가 병목이 되어 전체적인 성능 저하가 일어납니다.

 

WebFlux는 여리 리엑티브 방식 지원 서버 프레임워크(Jetty, Undertow, Tomcat 8.5 이상)를 사용할 수 있지만 기본은 Netty입니다.

Non-Blocking의 핵심 방식은 이벤트 루프 (Event Loop) 방식입니다.

 

 

용어 정리


자원 활용 방식(스레드 제어 방식)

  • Blocking
    • 하나의 작업이 완료 될때까지 스레드가 대기 하는 방식, 작업이 진행 되는 동안 해당 스레드는 다른작업을 하지 못합니다. 
    • 디버깅이 용이하고, 순차적 코드실행
  • Non-Blocking
    • 작업을 요청한 후 즉시 다음 작업으로 넘어가는 방식, 작업이 완료되면 콜백이나 이벤트를 통해 결과를 받습니다.
    • I/O 작업에서 높은 처리량

작업 처리 방식(실행 흐름 제어 방식)

  • Synchronous(동기)
    • 작업을 순차 적으로 처리, 하나의 작업이 완료된 이후 다음작업 진행
  • Asynchronous(비동기)
    • 작업 완료 시점과 결과 처리 시점이 다름
    • 코드의 실행 순서와 작업 처리 순서가 다를 수 있음

Spring WebFlux

  • 비동기, Non-Blocking 처리를 위한 스프링 프레임 워크.
  • Project Reactor 라이브러리를 기반(Mono와 Flux 타입).

Netty

  • 네트워크 라이브러리
  • Java NIO(New Input/Output) 기반으로 구축되어 논블로킹 I/O 연산
  • 하나의 스레드가 여러 연결을 동시에 처리할 수 있어 리소스 사용 효율성이 높습니다.
  • 이벤트 기반 아키텍처

 

기존 Blocking 서버(스레드 풀 방식)의 한계


1. 스레드 풀 한계

  • 클라이언트 연결이 오면 별도의 스레드를 할당해 요청을 처리한다.
  • 처리 도중 I/O(데이터 읽기/쓰기, DB 조회 등)가 발생하면 해당 스레드는 블로킹되어 대기 상태가 된다.
  • 스레드 풀(maxThreads)이 가득 차면, 새로운 연결 요청은 “풀에 빈 스레드가 생길 때까지” 기다려야 한다.

2. 리소스 비효율

  • 응답 지연(long I/O) 상황이 빈번할수록, 풀에 대기 중인 스레드가 많아지고 전체 처리량(throughput)이 떨어진다.
  • 대기 스레드가 많아지면, 새로운 클라이언트는 스레드가 반환될 때까지 응답 대기 시간이 길어진다.

 

EventLoop


  • 이벤트가 발생하면 Event Queue 에 등록
    • Event Queue
      • 이벤트 마다 어떤 처리를 할지 담당하는 헨들러와 함게 저장된다.
    • Envent Loop Thread
      • 새로운 이벤트가 들어왔나 체크하고 처리하는 스레드, 이벤트 발생 까지 대기 하고 발생하면 Handler에게 디스패치한다.
      • 하나의 스레드가 여러 이벤트를 순차적으로 논블록킹 방식으로 처리 효율적
        • Spring R2DBC 사용 : DB select 요청 이벤트 등록 후 핸들러가 요청만 보냄, 이후 DB select 응답 이벤트가 등록 되고 추후 로직을 이벤트 핸들러가 처리한다(효율적). JDBC 를 사용하게 되면 Blocking 방식이라 성능 저하 문제 발생한다.
    • Event Handler
      • 이벤트 발생시 무엇을 할지 정의한 코드
  • 장점
    • 소수의 쓰레드로 다수의 커넥션 처리: I/O가 블로킹되지 않기 때문에, 하나의 쓰레드가 수천 개 커넥션을 동시에 관리 가능
    • 낮은 컨텍스트 스위칭 비용: 스레드 수가 적어, 스레드 간 문맥 교환이 줄어듭니다.
    • 부하 분산(라운드 로빈): next() 호출 시 순환 방식으로 EventLoop를 선택하기 때문에, 채널 간 I/O 작업이 고르게 분배됩니다.
  • 단점 및 주의점
    • Blocking 코드 주의: EventLoop 내부에서 CPU 바운드 작업이나 블로킹 호출(예: 동기 파일 I/O, Thread.sleep 등)을 수행하면, 해당 EventLoop가 모든 채널의 I/O를 막아버립니다. 이를 피하기 위해 “작업이 오래 걸리는 로직”은 별도의 WorkerPool(예: DefaultEventExecutorGroup)에 위임해야 합니다.

 

참고