Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

디지안의 개발일지

Spring Webflux에 대하여 - 1편 본문

Spring

Spring Webflux에 대하여 - 1편

안덕기 2023. 3. 27. 19:48

개요

회사에서 Spring Webflux에 대해 발표를 할 기회가 있었어서 그 내용에 대해 정리하려고 한다.

웹요청 처리의 처리 방식

웹요청을 처리하기 위해서는 다음과 같은 과정이 필요하다.

  1. 클라이언트에게 받은 웹요청(네트워크 패킷)은 이진 데이터로 요청이 들어온다.
  2. 이 이진 데이터를 웹서버에서 처리할 수 있는 데이터로 변환하는 작업이 필요하다.
  3. 이진 데이터 → 바이트 → HTTP 객체로 변환하는 과정이 필요하다.

이 과정에서 네트워크 패킷(이진 데이터)를 바이트로 변환하는 과정은 커널 스레드가 바이트 값을 HTTP 객체로 변환하는 과정은 유저 스레드가 진행하게 되는데 이 과정을 누가, 어떻게 처리하는가에 따라 웹 기술이 발전하였다.

CGI(Common Gateway Interface)

1993년에 발표된 기술로 요청마다 프로세스를 생성하여 웹요청을 처리하는 방식이다.

C나 Perl과 같은 언어로 작성하였고 CGI.h와 같은 라이브러리를 통해 네트워크 패킷을 해석하였다.

웹요청 당 프로세스를 처리하는 방식이었기 때문에 메모리 비용이 엄청나고 프로세스 단위의 컨텍스트 스위칭이 일어나기 때문에 오버헤드도 컸을 것이다. 그렇기 때문에 적은 하드웨어 자원에서 트래픽이 많은 경우에는 요청을 해결할 수 없는 문제가 있었을 것이다.

Tomcat(BIO Connector + 서블릿 컨테이너 + 서블릿)

1999년에 톰캣이 처음 발표되면서 서블릿 컨테이너와 서블릿이 상호작용하면 웹요청을 처리하는 방식이 유행하기 시작했다. 톰캣은 기본적으로 웹요청 마다 스레드가 처리하는 방식이기 때문에 멀티스레드를 기반으로 동작하도록 설계되어 있다.

초창기 톰캣은 BIO(Blocking I/O) Connector를 통해서 이진 데이터를 HttpRequest로 변환하였다.

그 결과 CGI와 다르게 요청 당 스레드를 처리하기 때문에 더 많은 트래픽을 견딜 수 있었다.

하지만 톰캣의 한계가 있다.

  • 요청당 스레드를 처리하기 때문에 한번에 처리할 수 있는 요청의 갯수다
  • BIO Connector에서 이진 데이터를 바이트 값으로 변환하는 과정에서 Blocking이 발생한다.

BIO Connector에서 Blocking이 발생하는 과정을 보자.

  1. 웹요청을 받은 받은 톰캣은 해당 요청을 가지고 Socket을 만든다.
  2. Socket에는 Inputstream이 있는데 이를 통해 read() 하는 것을 통해 바이너리 값을 바이트 값으로 변경한다.
  3. 이 read() 메소드를 호출하는 것은 유저 스레드에서 이루어지는 것이 아니라 커널 스레드에서 이루어진다.
  4. 커널 스레드가 바이너리 값을 바이트 값으로 변경하는 동안 read() 메소드를 호출한 유저 스레드는 아무 것도 하지 않고 대기한다.

여기서 유저 스레드가 Blocking을 당하고 요청당 스레드를 처리하는 톰캣은 유저 스레드가 다른 일을 할 수 있는데도 커널 스레드를 기다리느라 아무것도 하지 못하고 낭비가 되는 것이다.

Tomcat(NIO Connector 등장)

톰캣에서는 Blocking 되는 과정을 해결하기 위해 2013년에 Nonblocking I/O를 선보였다. 이 전과 가장 큰 차이점은 다음과 같다.

  1. 요청이 들어왔을 때 바로 스레드에 할당하지 않는다.
  2. Poller라는 스레드가 SocketChannel을 생성하고 바로 InputStream을 통해 read() 메소드를 호출한다.
  3. 하지만 여기서 read() 메소드는 바로 리턴되고 SocketChannel은 캐싱이 된다.
  4. 그리고 Selector라는 스레드가 캐싱된 SocketChannel를 확인하면서 read() 메소드에 의해 읽기 시작한 바이트 값을 모두 읽었는지 확인한다.
  5. 모두 읽으면 그 때 비로소 스레드에 할당이 된다.

즉, 커널 스레드가 바이트 값으로 읽은 것에 대한 유무를 별도의 스레드에게 하게 하고 톰캣이 가지고 있는 유저 스레드는 진짜 일을 해야할 때 사용함으로써 톰캣 유저 스레드가 idle 상태에 빠지게 하는 것을 막은 것이다.

Tomcat(비동기 서블릿)

톰캣에서는 2009년에 추가적으로 비동기 서블릿을 선보였다. 비동기 서블릿의 핵심은 다음과 같다.

  1. 일반적으로 웹요청에 대한 처리가 끝날 때까지 클라이언트와 서버 간의 연결을 유지해야한다.
  2. 비동기 서블릿은 요청에 대한 처리가 끝나기 전에 연결을 끊고
  3. 콜백을 통해 처리가 완료되면 서버에서 클라이언트 응답을 전달한다.
  4. 클라이언트와 서버의 연결을 줄일 수 있기 때문에 자원을 아낄 수 있고 그를 통해 더 많은 웹요청을 처리할 수 있다.

Spring MVC의 한계

여기까지 보면 Spring MVC를 사용하는 톰캣도 충분히 비동기 서블릿과 NIO Connector를 통해 async non-blocking을 모두 지원하니 굳이 Spring Webflux를 사용할 필요가 없지 않을까? 라는 생각이 들 수 있다.

다음은 Spring Webflux 공식 홈페이지 나오는 글이다.

이 글에 의하면 근본적으로 서블릿에서 처리하는 기능들인 동기식 처리(Filter, Servlet)은 블로킹 방식(getParameter, getPart) 때문에 새로운 API를 개발하는 것이 필요했다고 한다.

심지어 NIO Connector를 사용하기 위해서는 간단한 설정만 하면 되지만 비동기 서블릿을 활용하려면 아래와 같이 코드를 작성해야한다.

Spring Webflux

Spring Webflux와 Spring MVC와 다른 주요 차이점은 다음과 같이 두가지다.

  • Netty라는 네트워크 프레임워크를 사용하여 Non-Blocking 지원한다.
  • Reactor라는 비동기 모델을 사용한다.

Netty의 웹 요청 과정

Netty가 웹 요청을 처리하는 과정(이진 데이터 → 바이트 → HTTP 객체 → 비즈니스 로직)의 핵심은 이벤트 루프에 있다. 이벤트 루프가 주기적으로 이벤트를 확인하고 I/O 스레드와 유저 스레드가 놀지 못하도록 계속 일을 시키는 것이 핵심이다. 웹 요청을 처리하는 과정에 대해 요약하면 다음과 같다.

  1. Selector에 의해 이벤트가 감시된다.
  2. 이벤트가 감지되면 이벤트 루프에게 이벤트를 전달한다.
  3. Selector가 전달한 이벤트를 이벤트 루프가 이벤트 큐에 이벤트를 등록한다.
  4. 등록된 이벤트는 다시 이벤트 루프에 의해 처리되는데 이 때 이벤트 루프는 I/O 스레드에게 이 일을 맡긴다.
  5. 이 일은 네트워크 패킷을 바이트 값으로 변환하는 작업이며 이벤트 루프는 직접 커널 스레드에게 일을 시키는 것이 된다.
  6. 이벤트 루프는 커널 스레드에게 일을 위임만 하고 있고 커널 스레드들은 이벤트 루프에게 계속적으로 일을 받아 요청이 계속 온다면 idle 상태에 빠지는 일이 없다.
  7. I/O 처리가 완료되면 이벤트 핸들러가 비즈니스 로직을 수행한다.
  8. 이 때 비즈니스 로직은 유저 스레드에 의해 작업이 이루어진다.
  9. 비즈니스 로직이 마무리되면 다시 이벤트 루프에게 전달되어 이벤트로 등록된다.
  10. 그리고 이벤트 루프에 의해 다시 커널 스레드에게 네트워크 패킷으로 변환하는 작업을 시킨다.

이 과정을 통해 각 스레드들이 idle 상태로 빠지는 것을 방지하여 CPU 유휴 시간을 없애는 것이 Netty의 핵심이라고 보인다.

여기에 추가적으로 바이트 값 같은 경우 ByteBuf라는 새로운 타입을 사용하여 메모리에 최적화를 하고 있다.

다음에는 Spring Webflux에서 사용하는 타입은 Reactor에 대해 알아보려고 한다.

참고자료
https://velog.io/@jihoson94/BIO-NIO-Connector-in-Tomcat

 

BIO, NIO Connector Architecture in Tomcat

Servlet 과 Servlet Container를 소개할 때 대표적인 Servlet Container인 Tomcat을 소개했습니다. Tomcat의 Servlet Container Framework인 Catalina가 핵심적인 요소입니다. 하지만 Tomcat에서 핵심적

velog.io

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

 

Spring WebFlux (1)

스프링5 웹 리액티브 스택 웹플럭스 한글 번역 1편 (리액티브, 웹플럭스 소개, DispatcherHandler, 컨트롤러)

godekdls.github.io

 

'Spring' 카테고리의 다른 글

Kotlin으로 RestDocs DSL 만들기  (0) 2023.08.06