Spring Webflux
- Spring Webflux 탄생 배경
- Spring MBC의 한계
- Servlet 기반의 Blocking IO 방식
- 하나의 요청을 사용하기 위해 하나의 스레드를 사용하고, 해당 스레드의 작업이 끝나기 전까지 스레드는 block 된다.
- 대량의 요청을 안정적으로 처리하기 위해 Non-Blocking 형태의 Spring Webflux 탄생
- Spring MBC의 한계
- Webflux 기술 스택
Servlet Containers <-> Netty Servlet 3.1+ Containers Servlet API <-> Reactive Streams Adapter Spring Security <-> Spring Security Reactive Spring MVC <-> Spring Webflux Spring Data Reposiotries(JDBC, JPA, NoSQL) <-> Spring Data Reactive Repository(R2DBC, NoSQL)
- 서버
- Spring MVC는 Tomcat과 같이 Servlet Container에서 Blocking IO 방식으로 동작
- Webflux는 Non Blocking을 동작하는 Netty 등의 서버 엔진 사용
- 서버 API
- Webflux는 기본 서버 엔진이 Netty 지만, Jetty나 Undertow 같은 서버 엔진에서 지원하는 리액티브 스트림즈 어댑터를 통해 리액티브 스트림즈 지원
- 보안
- WebFilter를 이용해 Spring Security를 Webflux에서 사용할 수 있다.
- 데이터 액세스
- Non-Blocking IO를 지원할 수 있도록 R2DBC 및 NoSQL 모듈 사용
- 서버
- 요청 처리 흐름
- 최초에 요청이 들어오면 Netty 등의 서버 엔진을 거쳐 HttpHandler가 전달 받는다. Handler는 다양한 서버 엔진을 지원할 수 있도록 추상화해주는 역할 a. 각 서버엔진마다 주어지는 ServerHttpRequest, ServerHttpResponse를 포함하는 ServerWebExchange를 생성한 후 WebFilter 전달
- ServerWebExchange는 WebFilter 체인에서 전처리 과정을 거친 후, WebHandler 인터페이스 구현체인 DispatcherHandler에게 전달
- DispatcherServlet과 유사한 역할을 하는 Dispatcher Handler에서는 HandlerMapping List를 원본 Flux의 소스로 전달 받는다.
- ServerWebExchange를 처리할 핸들러를 조회
- 조회한 핸들러의 호출을 HandlerAdapter에게 위임
- HandlerAdapter는 ServerWebExchange를 처리할 핸들러 호출
- Controller 또는 HandlerFunction 형태의 핸들러에서 요청을 처리한 후 응답 데이터를 리턴
- 핸들러로부터 리턴받은 응답 데이터를 처리할 HandlerResultHandler를 조회
- 조회한 HandlerResultHandler가 응답 데이터를 처리한 후 response 리턴
- 핵심 컴포넌트
- HttpHandler
public interface HttpHandler { Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response); } public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHandler { Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) { // ... ServerWebExchane exchange = createExchange(request, response); // ... } }
- WebFilter
public interface WebFilter { Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain); } @Component public class BookLogFilter implements WebFilter { Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); return chain.filter(exchange).doAfterTerminate(() -> { if(path.contains("books") { System.out.println("path: " + path); } }); } } }
- Servlet Filter 처럼 핸들러가 요청을 처리하기 전에 전처리 작업을 할 수 있도록 해준다.
- WebFilterChain을 통해 필터 체인을 형성하여 원하는 만큼의 필터를 추가할 수 있다.
- HandlerFilterFunction
@FuntionalInterface public interface HandlerFilterFunction<T extends ServerResquest, R extends ServerResponse> { Mono<R> filter(ServerWebRequest request, HandlerFunction<T> next); /// } public class BookRouterFunctionFilter implements HandlerFilterFunction { @Override Mono<R> filter(ServerWebRequest request, HandlerFunction<T> next) { String path = exchange.getRequest().getURI().getPath(); return next.handler(exchange).doAfterTerminate(() -> { if(path.contains("books") { System.out.println("path: " + path); } }); } } @Configuration public class BookRouterFunction { return RouterFunctions .route(GET("v1/router/books/{bookId}), (ServerRequest) request -> this.getBook(request)) .filter(new BookRouterFunctionFilter()); }
- 함수형 기반의 요청 핸들러에 적용할 수 있는 Filter
- WebFilter 구현체는 Spring Bean으로 등록되는 반면 HandlerFilterFunction 구현체는 애너테이션 기반의 핸들러가 아닌 함수형 기반의 요청 핸들러에서 함수 형태로 사용되기 때문에 bean으로 동작하지 않는다.
- DispatcherHandler
- WebHandler 구현체로 Front Controller 패턴이 적용되어 먼저 요청을 전달받은 후 다른 컴포넌트에 요청 처리 위임
- DispatchHandler 자체가 Bean으로 등록되어 동작하며, HandlerMapping, HandlerAdapter, HandlerResultHandler 등의 요청 처리를 위한 위임 컴포넌트 검색
- HandlerMapping
public interface HandlerMapping { Mono<Object> getHandler(ServerWebExchange exchange); }
- MVC와 동일하게 request와 handler object에 대한 매핑을 정의하는 인터페이스이며, HandlerMapping 인터페이스를 구현하는 구현클래스로 RequestMappingHandlerMapping, RouterFunctionMapping 등이 있다.
- HandlerAdapter
public interface HandlerAdapter { boolean supports(Object handler); // 파라미터로 전달받은 handler object를 지원하는지 체크 Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler); // 파라미터로 전달받은 handler object를 통해 핸들러 메서드 호출 }
- HandlerMapping을 통해 얻은 핸들러를 직접 호출하는 역할을 하며 HandlerResult를 리턴받는다.
- RequestMappingHandlerAdapter, HandlerFunctionAdapter, SimpleHandlerAdapter, WebSocketHandlerAdapter 존재
- HttpHandler
- Non-Blocking 프로세스 구조
- Blocking IO 방식의 MVC는 요청을 처리하는 스레드가 차단될 수 있기 떄문에 기본적으로 대용량의 스레드 풀을 사용해 하나의 요청을 하나의 스레드가 처리
- NonBlocking IO 방식은 WebFlux는 스레드가 차단되지 않기 때문에 적은 수의 고정된 스레드 풀을 사용해 더 많은 요청을 처리
- 스레드 차단 없이 더 많은 요청을 처리할 수 있는 이유는 Event Loop 방식 때문
- 요청을 요청 핸들러가 전달받는다.
- 전달받은 요청을 이벤트 루프에 푸시하면 이벤트 루프는 네트워크, DB 연결 작업 등 비용이 드는 작업에 대한 콜백 등록
- 작업이 완료되면 완료 이벤트를 이벤트 루프에 푸쉬
- 등록한 콜백을 호출해 처리 결과 전달
- 요청 처리, DB 연결과 같은 IO 작업을 이벤트로 처리하기 때문에 이벤트에 대한 콜백을 등록하고 동시에 다음 이벤트 처리로 넘어간다.
- 스레드 모델
- NonBlocking IO를 지원하는 Netty등의 서버 엔진에서 작은 수의 고정된 크기의 스레드를 생성하여 대량의 요청 처리
- Reactor Netty에서는 CPU 코어의 개수가 4보다 작은 경우 최소 4개의 워커 스레드를 생성하고, 4보다 더 많다면 코어 개수만큼 스레드 생성
- 적은 수의 스레드로 대량의 요청을 효과적으로 처리할 수 있다.
- 클라이언트 요청부터 응답 처리과정 안에서 Blocking되는 지점이 존재한다면 오히려 성능이 저하된다.
- Scheduler 를 통해 서버 엔진에서 제공하는 스레드 풀이 아닌 다른 스레드 풀을 사용한다.