Spring Webflux 소개 – 1부

개요

회사에서 Spring Webflux에 대한 프레젠테이션을 할 기회가 있어서 내용을 정리해 보겠습니다.

웹 요청 처리 방법

웹 요청을 처리하려면 다음 단계가 필요합니다.

  1. 클라이언트로부터 받은 웹 요청(네트워크 패킷)은 바이너리 데이터로 도착합니다.
  2. 이 바이너리 데이터는 웹 서버에서 처리할 수 있는 데이터로 변환되어야 합니다.
  3. 바이너리 → 바이트 → HTTP 객체 변환 과정이 필요합니다.


이 과정은 네트워크 패킷(바이너리 데이터)을 바이트로 변환하는 과정입니다. 커널 스레드바이트 값을 HTTP 객체로 변환하는 프로세스는 다음과 같습니다. 사용자 스레드실행 중, 이 프로세스 누가 어떻게 웹 기술은 처리 방식에 따라 발전했습니다.

공통 게이트웨이 인터페이스(CGI)

1993년에 발표된 기술로 각 요청에 대한 프로세스를 생성하여 웹 요청을 처리합니다.

C 또는 Perl과 같은 언어로 작성되었으며 네트워크 패킷은 CGI.h와 같은 라이브러리를 통해 해석되었습니다.

웹 요청당 1프로세스 방식이었기 때문에 메모리 비용이 엄청났고 각 프로세스에서 컨텍스트 전환이 일어나기 때문에 오버헤드가 컸을 것이다. 따라서 작은 하드웨어 리소스로 트래픽이 많은 상황에서 요청을 해결할 수 없는 문제가 있었을 수 있습니다.

Tomcat(BIO 커넥터 + 서블릿 컨테이너 + 서블릿)

1999년 Tomcat이 처음 출시되었을 때 서블릿 컨테이너가 서블릿과 상호 작용할 때 웹 요청을 처리하는 방법이 대중화되기 시작했습니다. Tomcat은 기본적으로 모든 웹 요청이 하나의 스레드에서 처리되기 때문에 다중 스레드 기반에서 작동하도록 설계되었습니다.

초기에 Tomcat은 BIO(Blocking I/O) 커넥터를 통해 바이너리 데이터를 HttpRequest로 변환했습니다.

결과적으로 CGI와 달리 요청당 하나의 스레드를 처리하기 때문에 더 많은 트래픽을 견딜 수 있습니다.

그러나 Tomcat에는 한계가 있습니다.

  • 요청당 하나의 스레드가 처리되므로 동시에 처리할 수 있는 요청 수입니다.
  • BIO 커넥터에서 바이너리 데이터를 바이트 값으로 변환할 때 차단이 발생합니다.

BIO Connector에서 차단하는 과정을 살펴보자.

  1. 웹 요청을 받으면 Tomcat은 요청으로 소켓을 생성합니다.
  2. 소켓에는 read()를 사용하여 이진 값을 바이트 값으로 변환하는 입력 스트림이 포함되어 있습니다.
  3. 이 read() 메서드는 사용자 스레드가 아닌 커널 스레드에서 호출됩니다.
  4. 커널 스레드가 바이너리 값을 바이트 값으로 변환하는 동안 read() 메서드를 호출한 사용자 스레드는 아무 작업도 수행하지 않고 대기합니다.

여기서 사용자 스레드는 차단되고 요청당 스레드를 처리하는 tomcat은 아무 작업도 수행하지 않고 사용자 스레드가 다른 작업을 수행할 수 있지만 커널 스레드를 기다리며 낭비됩니다.

Tomcat(NOK 커넥터 포함)

Tomcat은 블로킹 프로세스를 해결하기 위해 2013년에 Nonblocking I/O를 도입했습니다. 이전과 가장 큰 차이점은 다음과 같습니다.

  1. 요청이 들어오면 즉시 스레드에 할당되지 않습니다.
  2. Poller라는 스레드는 SocketChannel을 만들고 즉시 InputStream에서 read() 메서드를 호출합니다.
  3. 그러나 여기서는 read() 메서드가 즉시 반환되고 SocketChannel이 캐시됩니다.
  4. 그런 다음 selector라는 스레드는 캐시된 SocketChannel을 검사하고 read() 메서드에 의해 읽기 시작한 모든 바이트가 읽혔는지 확인합니다.
  5. 모두 읽히면 스레드에 할당됩니다.

즉, 커널 쓰레드가 바이트 값을 읽었는지 여부를 별도의 쓰레드가 판단하도록 하고, Tomcat이 실제 작업을 수행하는 데 필요한 사용자 쓰레드를 사용함으로써 Tomcat user-Thread가 유휴 상태에 빠지는 것을 방지한다.

Tomcat(비동기 서블릿)

Tomcat은 2009년에 추가 비동기 서블릿을 도입했습니다. 비동기 서블릿의 본질은 다음과 같습니다.

  1. 일반적으로 클라이언트와 서버 간의 연결은 웹 요청이 처리될 때까지 유지되어야 합니다.
  2. 비동기 서블릿은 요청을 처리하기 전에 연결을 닫습니다.
  3. 콜백 처리가 완료되면 서버는 클라이언트 응답을 전송합니다.
  4. 클라이언트와 서버 간의 연결을 줄일 수 있으므로 리소스를 절약하고 더 많은 웹 요청을 처리할 수 있습니다.

스프링 MVC의 한계

지금까지 Spring MVC를 사용하는 Tomcat은 NIO 커넥터를 통해 비동기 서블릿과 비동기 비차단을 모두 지원하므로 Spring Webflux가 필요하지 않습니다. 당신은 그렇게 생각할 수 있습니다.

다음은 Spring Webflux 공식 사이트의 기사입니다.


이 기사에 따르면 기본적으로 서블릿에서 처리하는 기능인 동기 처리(필터, 서블릿)의 차단 메서드(getParameter, getPart)로 인해 새로운 API를 개발해야 했습니다.

NIO Connector를 사용하기 위해서는 간단한 설정이라도 필요하지만, 비동기 서블릿을 사용하기 위해서는 다음과 같이 코드를 작성해야 합니다.


스프링 웹플럭스

Spring Webflux와 Spring MVC에는 두 가지 주요 차이점이 있습니다.

  • 논블로킹은 Netty라는 네트워크 프레임워크에서 지원합니다.
  • Reactor라는 비동기 모델을 사용합니다.

Netty의 웹 요청 프로세스

웹 요청을 처리하는 Netty 프로세스의 핵심(바이너리 데이터 → 바이트 → HTTP 객체 → 비즈니스 로직)은 이벤트 루프핵심은 이벤트 루프가 정기적으로 이벤트를 확인하고 I/O 및 사용자 스레드가 유휴 상태가 되지 않도록 하는 것입니다. 웹 요청을 처리하는 프로세스를 요약하면 다음과 같습니다.

  1. 유권자이벤트는 다음에 의해 모니터링됩니다.
  2. 이벤트가 감지되면 이벤트 루프에 이벤트를 전달합니다.
  3. 이벤트 루프는 셀렉터가 전달한 이벤트를 이벤트 큐에 등록합니다.
  4. 등록된 이벤트는 이벤트 루프에 의해 다시 처리되며, 이벤트 루프는 이 작업을 I/O 스레드에 위임합니다.
  5. 이 작업은 네트워크 패킷을 바이트 값으로 변환하는 것으로 구성되며 이벤트 루프는 커널 스레드에 작업을 직접 지시합니다.
  6. 이벤트 루프는 작업을 커널 스레드에만 위임하고 커널 스레드는 계속해서 이벤트 루프에서 작업을 받기 때문에 요청이 계속 들어오는 한 유휴 상태에 빠지지 않습니다.
  7. I/O 처리가 완료되면 이벤트 핸들러가 비즈니스 로직을 실행합니다.
  8. 이 시점에서 비즈니스 로직은 사용자 스레드에 의해 실행됩니다.
  9. 비즈니스 로직이 준비되면 이벤트 루프로 반환되고 이벤트로 등록됩니다.
  10. 그런 다음 이벤트 루프는 커널 스레드에 이를 네트워크 패킷으로 바꾸라고 지시합니다.

Netty의 본질은 이 과정을 통해 어떤 쓰레드도 유휴 상태로 떨어지는 것을 막아 CPU 유휴 시간을 없애는 데 있는 것 같다.

또한 바이트 값이 바이트버프라는 새로운 유형을 만들어 메모리를 최적화합니다.

다음으로 Spring Webflux에서 사용하는 타입은 Reactor이다.

참조
https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat

Tomcat의 BIO, NIO 커넥터 아키텍처

서블릿과 서블릿 컨테이너의 등장으로 대표적인 서블릿 컨테이너인 톰캣이 등장했다. Tomcat의 서블릿 컨테이너 프레임워크인 Catalina는 핵심 구성 요소입니다. 그러나 Tomcat에서는 핵심

velog.io

https://godekdls.github.io/Reactive%20Spring/springwebflux/

스프링 웹플럭스 (1)

Spring 5 Web Reactive Stack WebFlux 한국어 번역 Part 1 (Reactive, WebFlux 소개, DispatcherHandler, Controller)

godekdls.github.io