• 스프링 부트 - WebFlux의 Non-Blocking 프로세스 구조

    2025. 1. 9.

    by. Daramu

    Spring MVC는 전통적인 Block I/O 방식으로, 일정량의 스레드 풀에서 요청이 들어올 경우 스레드를 할당하여 요청을 처리한다.

     

    즉, 해당 스레드가 완전히 종료될때까지 스레드를 사용할 수 없다는 의미이며, 모든 스레드가 가득 찼을때 새로운 요청이 온다면 해당 스레드가 차단될 수 있다는 의미이다.

     

    반면 Non-Blocking I/O 방식의 Spring WebFlux는 스레드가 차단되지 않기 때문에 적은 수의 고정된 스레드 풀을 사용하여 많은 요청 처리가 가능하다.

     

    이것이 가능한 이유는 WebFlux는 이벤트 루프 방식을 사용하기 때문이다.

     

    Non-Blocking 방식은 아래와 같은 순서로 동작한다.

     

    1. 클라이언트로부터 들어오는 요청을 핸들러가 전달 받는다.

    2. 전달받은 요청을 이벤트 루프에 푸시한다.

    3. 이벤트 루프는 네트워크, 데이터베이스 연결 작업 등 비용이 드는 작업에 대한 콜백을 등록한다.

    4. 작업이 완료되면 완료 이벤트를 이벤트 루프에 푸시한다.

    5. 등록한 콜백을 호출해 처리 결과흫 전달한다.

     

    이벤트 루프는 단일 스레드에서 계속 실행되며, 클라이언트의 요청이나 데이터베이스 I/O, 네트워크 I/O 등 모든 작업들이 이벤트로 처리된다.

     

    결과적으로 이벤트 루프 방식을 도입함으로써 적은 수의 스레드로 많은 요청을 Non-Blocking 방식으로 처리가 가능한 것이다.

     

    WebFlux의 스레드 모델

    MVC 패턴의 경우, 클라이언트의 요청이 들어올때마다 서블릿 컨테이너의 스레드 풀에 미리 생성되어 있는 스레드가 요청을 처리하고, 요청 처리를 완료하면 스레드 풀이 반납되는 모델을 사용한다. 

     

    즉, 요청마다 스레드를 사용하기에 많은 요청 처리를 위해서는 많은 스레드가 필요한 것이다.

     

    반면 WebFlux는 Non-Blocking I/O를 지원하는 Netty등의 서버 엔진에서 적은 수의 고정된 크기의 스레드를 생성해서 대량의 요청을 처리한다.

     

    아래는 Reactor Netty의 LoopResources 인터페이스 코드의 일부이다.

    CPU 코어가 4개보다 적다면 최소 4개의 워커 스레드를 생성하고, 그보다 많다면 코어 갯수 만큼의 워커 스레드를 생성한다.

    @FunctionnalInterface
    public interface LoopResources extends Disposable{
    /**
    기본 스레드 수는, CPU 코어의 갯수만큼 생성된다.
    **/
    int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getPropoerty(ReactorNetty.OP_WORKER_COUNT,"" + math.max(Runtime.getRuntime().availableProcessors(),4)));
    ...
    ...
    }

     

    댓글