[Spring Webflux] 스프링 웹플럭스 (MVC와 비교, 내부 동작 원리, Netty)
황정식 - 스프링으로 시작하는 리액티브 프로그래밍
Spring WebFlux를 이용한 Non-Blocking 애플리케이션 구현
책을 통해 공부하면서 개인적으로 정리한 내용입니다.
목차
- Spring Webflux란?
- Spring Webflux 탄생 배경
- 예시를 통해 MVC와 WebFlux를 비교해보자
- Spring Webflux Stack
- 전체 큰 그림
- Spring Webflux의 내부 동작 원리
- Netty의 역할
✅ Spring Webflux란?
Spring webflux는 리액티브 웹 애플리케이션 구현을 위해 Spring 5.0부터 지원되는 Reactive Web Framework
✅ Spring WebFlux 탄생 배경
⏺ Spring MVC
서블릿기반의 Blocking I/O방식
요청당 하나의 스레드를 사용, 스레드의 작업이 끝날 때 까지 스레드가 차단됨
😵 Spring MVC의 한계
대용량 요청 트래픽을 Spring MVC방식이 처리하기엔 한계가 있었다.
1) 트래픽이 많아지면 많아질수록 스레드도 많이 사용되는데,
2) 스레드풀에 스레드 200개가 default로 존재하고, 만약 만명이 동시접근한다...? 와우.....
3) 스레드 스위칭 비용도 그만큼 많이 발생한다
⏺ Spring Webflux를 이용한 극복
대용량 트래픽을 감당하기 위해선, 비동기/논블로킹 방식의 I/O를 사용해야했으며
이 방식이 적용되어, 대용량도 안정적으로 처리할 수 있는 Spring WebFlux가 생겨났다
✍️ 예시를 통해 MVC와 Webflux의 차이를 명확하게 이해해보자
client가 백엔드 서버로 요청을 보내면,
백엔드 서버는 외부 서버로 요청을 보내게 되는데
이때 외부 서버의 동작이 5초 걸린다고 가정하자.
client의 요청은 총 5번 들어왔다.
1️⃣ MVC의 경우
MVC는 서블릿 기반 동기식 처리를 한다.
외부 서버로 요청을 보낼 때 동기 처리를 하는 RestTemplate을 사용해서 처리를 한다고 했을 때
5번(요청) * 5초(외부 서버의 응답 처리시간) == 총 25초가 걸릴것이다.
외부 서버와 통신할 때 요청처리스레드는 블로킹(Blocking) 된다.
2️⃣ WebFlux의 경우
웹플럭스는 비동기 넌블로킹 방식의 리액티브 프로그래밍을 지원한다.
이때 외부 서버로 요청을 보내면서 WebClient를 사용하면 >
5번의 요청에서 블로킹이 발생하지 않는다.
무슨 의미냐면
외부 서버에서 5초 걸리는것을 우리서버에서 기다리지 않는다는 것이다.
블로킹이 발생해서 25초 걸렸던 MVC와 비교했을떄
대용량 처리가 필요한 상황에서 아주 빠른 처리를 할 수 있다.
✅ Spring WebFlux Stack
> Netty (기본 서버엔진)
> 리액티브 스트림즈 어댑터를 통한, 리액티브 스트림즈 지원
> WebFilter (Spring Security 사용)
> NoSQL모듈 사용 (Spring Data R2DBC, Non-Blocking I/O 지원)
✅ 전체 큰 그림
내가 열심히 그린 그림.... *(사실 이미지 2개 합친거다 😁😁)
아래에 세부 설명을 보고 다시 이그림으로 돌아오자.
✍️ Spring Webflux의 내부 동작 원리
Client의 요청이 발생하면 서버엔진인 Netty를 거치게 된다.
- HttpHandler가 서버 API를 추상화한다.
- 네티 뿐만 아니라 다양한 서버엔진이 지원된다.
- ServerWebExchange를 생성한다. 이 친구한테 ServletHttpRequest, ServletHttpResponse가 포함되어있다.
- WebFilter
- 필터체인으로 구성된 웹 필터이다.
- ServerWebExchange의 전처리 과정을 실천한다.
- 이후 Web handler의 구현체인 Dispatcher Handler에게 전달된다.
- DispatchHandler
- SpringMVC의 Dispatcher Servlet과 유사한역할
- Handler Mapping에게서 핸들러매핑 리스트를 받아서, 원본 Flux의 소스로 전달받는다.
- getHandler
- ServerWebExchanger를 처리할 핸들러를 조회한다.
- 핸들러 어댑터에게, ServerWebExchanger를 처리하도록 위임한다.
- HandlerAdapter
- 핸들러어댑터는 핸들러를 호출한다.
- 이때 호출되는 핸들러의 형태는 Controller, Handler Function 형태이며, Mono<HandlerResult>를 반환한다.
- 반환받은 응답데이터를 처리할 HandlerResultHandler를 조회한다.
- HandlerResultHandler
- 응답 데이터의 적절한 처리 후 return한다.
✍️ Netty의 역할
- 네티는 서버엔진으로 네트워크 I/O 이벤트에 대한 콜백 함수를 등록하고 관리한다.
- 콜백 함수는 특정 이벤트가 발생할 때 자동으로 호출된다.
- 요청처리 방식을 이벤트 루프 방식으로 사용하였고, 이벤트 루프는 단일스레드를 사용한다.
🤔 HttpHandler에 가기전 서버엔진에서는 어떤 일이 일어날까?
- 먼저 Client들의 요청은 요청핸들러를 거친다.
- 요청에 대한 event를 생성하고, 이벤트 루프에 넣는다(push)
- 이벤트 루프는 (비용이 드는) 작업에 대한 콜백을 등록한다.
- 작업이 완료된 것은 완료 이벤트를 이벤트 루프에 push한다. 이를 통해 원래 콜백이 실행된다.
(콜백이 작업의 결과를 처리한다) - 등록된 콜백이 호출해 처리 결과를 전달한다. 결과가 클라이언트에게 반환된다.
🤔 스레드의 개수 결정은?
- 내부 CPU 코어 수에 의해 결정된다.
WHY?
- 각 이벤트 루프는 독립적인 스레드에서 실행되는데
- 이때 너무 많은 스레드는 스레드스위칭 비용을 증가시키고
- 너무 적은 스레드는 CPU를 충분하게 활용할 수 없기 때문에
- 내부적인 CPU 코어의 수로 결정하도록 설정되어있다.
references