Spring/WebFlux

[Spring WebFlux] 기존 웹 서버와의 차이점과 꼭 주의해야할 점 (Netty)

alska95 2024. 11. 18. 20:03

1. Netty와 Tomcat의 차이

Spring WebFlux는 논블로킹 리액티브 프로그래밍 모델을 지원하며 기본적으로 Netty를 웹 서버로 사용한다.

이는 블로킹 기반 웹 서버인 Tomcat과는 스레드 관리 방식 등 다소 차이가 있기에 그 차이를 이해해야만 효율적인 reactive 프로그래밍이 가능하다.

(Tomcat과 같은 서블릿 컨테이너를 활용해서도 Flux와 Mono data stream을 반환할 수 있기는 하다.)

 

 

2. 이벤트 루프를 사용하는 Netty의 특성

Netty는 이벤트 루프 기반의 네트워크 애플리케이션 프레임워크로, 논블로킹 I/O 작업을 효율적으로 처리하기 위해 설계되었다.

Tomcat은 스레드 풀을 두고 다수의 스레드를 만들어두고 개별 요청마다 스레드를 할당한다.

반면에 Netty는 쓰레드를 기본적으로 CPU 코어 수 × 2 만큼 생성되고 이를 여러 요청에서 재사용한다.

 

이벤트 루프 기반 아키텍처의 주요 특성은 다음과 같다.

  • 소수의 스레드(이벤트 루프 스레드)가 모든 연결과 요청을 처리한다.
  • 이벤트 루프 스레드에서 요청을 이벤트 큐로 관리하며 여러 이벤트를 처리하기 때문에 스레드 간 컨텍스트 전환이 적다.
  • 요청 대기 상태에서 낭비되는 쓰레드가 없고 적은 수의 스레드로도 아주 많은 수의 요청을 처리할 수 있다.

 

3. 이벤트 루프를 이해하는것의 중요성

이벤트 루프 모델은 높은 동시성을 제공하지만 앞서 언급했듯이 쓰레드 수가 제한적이기 때문에

이벤트 루프 스레드에서 I/O라던지 Blocking 작업이 발생하면 전체 성능에 심각한 영향을 미친다.

 

예를 들어 아래와 같이 5초가 걸리는 유저 검색 작업이 있다고 가정해보자.

@GetMapping("/findUser")
public Mono<String> findUserExample() {
    return Mono.fromCallable(() -> {
        Thread.sleep(5000); // 5초 대기
        return "User found";
    });
}

 

위처럼 요청이 들어온 스레드에서 그대로 작업을 한다면 이벤트 루프 스레드에서 작업이 이루어지게 된다.

이벤트 루프 스레드에서 I/O 작업이 이루어지기 때문에 5초간 이벤트 루프 스레드가 점유당하게 된다.

만약에 Netty서버에 이벤트 루프 쓰레드가 4개만 생성되어 있다면,

4명의 유저가 동시 접속하면 나머지 유저들은 5초간 아무런 응답도 받을 수 없게 된다.

이는 5초에 4명의 유저만 처리할 수 있는 아주 비효율적인 웹 서비스가 될 것이다.

 

어떻게 방지할 수 있을까?

 

I/O 작업을 별도의 쓰래드로 분리해야한다.

publishOn, subScribeOn 등을 사용하여 Scheduler을 설정하면 쓰레드를 이벤트 루프 쓰레드로부터 분리할 수 있다.

 

다음과 같이 코드를 수정할 수 있다.

@GetMapping("/findUser")
public Mono<String> findUserExample() {
    return Mono.fromCallable(() -> {
        Thread.sleep(5000);
        return "User found";
    }).subscribeOn(Schedulers.boundedElastic()); // 블로킹 작업을 별도 스레드 풀에서 실행
}

 

이렇게 하면 이벤트 루프 쓰레드는 바로 유저에게 응답을 보내고 다른 응답을 처리할 수 있게 된다.

 

기억해야 할 것은 이벤트 루프 쓰레드에서는 절대 Blocking을 유발할 수 있는 요소가 실행되어서는 안 된다는 것이다.

Blocking을 유발하는 작업에 Scheduler을 설정해서 쓰래드를 할당해서 해결할 수 있다.