Spring WebFlux 탄생 배경
- 리액티브 웹 애플리케이션 구현을 위해 Spring 5.0부터 지원하는 리액티브 웹 프래임워크
- 기술의 발달에 따른 대량의 요청 트래픽을 Spring MVC 방식이 처리하지 못하는 상황이 잦아짐에 따라 적은 수의 쓰레드로 대량의 요청을 안정적으로 처리할 수 있는 비동기 Non-Blocking I/O 방식의 Spring WebFlux가 탄생
Spring Reactive Stack
주요 구성 요소 | 설명 |
Netty, Servlet 3.1+ Containers | Netty: 네트워크 애플리케이션 프레임워크로 고성능 non-blocking 서버를 구현하는데 사용 Servlet 3.1+ Containers: Servlet API 3.1 이상을 지원하는 컨테이너로 non-blocking I/O를 지원 |
Reactive Stream Adapters | 리액티브 스트림 사양을 구현하여 다양한 리액티브 라이브러리와의 상호 운용성 제공 |
Spring Security Reactive | Spring Security의 리액티브 버전으로 비동기 및 non-blocking 방식으로 보안 기능 제공 |
Spring WebFlux | 스프링 프레임워크에서 제공하는 리액티브 웹 프레임워크로 non-blocking 방식의 REST API를 구현 Reactor(Mono, Flux)를 기반으로 하며, 비동기 데이터 스트림을 처리 가능 |
Spring Data Reactive Repositories | 비동기 방식으로 DB에 접근하는 레포지토리로 MongoDB, Cassandra, Redis, Couchbase, R2DBC 등 다양한 DB를 지원 |
WebFluxAutoConfiguration
- Spring WebFlux 사용을 위해 필수로 구성해야 하는 AutoConfiguration
- @AutoConfiuration 어노테이션의 after 속성에 ReactiveWebServerFactoryAutoConfiguration이 있는 것을 확인 가능
1. ReactiveWebServerFactoryAutoConfiguration
- 내부적으로 EmbeddedTomcat, EmbeddedJetty, EmbeddedUndertow, EmbeddedNetty를 import 하는 것을 확인 가능
- @ConditionalOnClass 어노테이션으로 인해 기본적으로 EmbeddedNetty만 Bean으로 등록
- spring-boot-starter-webflux dependency 추가 시 spring-boot-starter-reactor-netty도 추가됨
2. NettyReactiveWebServerFactory
- Spring Boot에서 Netty 기반의 리액티브 웹 서버를 설정하고 실행하기 위해 사용하는 클래스
- ReactiveWebServerFactory 인터페이스를 구현하여 Netty를 기반으로 하는 웹 서버를 생성하고 관리
- Reactor Netty의 HttpServer 클래스를 기반으로 ReactorHttpHandlerAdapter를 생성한 뒤 최종적으로 NettyWebServer를 생성하는 것을 확인 가능
Reactor Netty
- Reactor를 기반으로 Netty를 Wrapping한 라이브러리
- Reactor Netty는 다음의 장점을 제공
- Netty의 고성능
- Reactor의 조합성 및 편의성
- Reactor Netty에서는 별도의 channel과 EventLoopGroup을 명시하지 않음
- read I/O event가 완료되었을 때
- Netty에서는 파이프라인에 ChannelHandler cnrk
- Reactor Netty에서는 HttpHandler를 구현하여 ReactorHttpHandlerAdapter로 Wrapping 해서 전달
Reactor Netty 예제
부연 설명
- Reactor의 Mono 및 Flux를 사용하여 비동기 데이터 흐름을 쉽게 조작하고 조합할 수 있음
- Reactor Netty는 Netty의 저수준 API를 추상화하여 더 직관적이고 사용하기 쉬운 고수준 API를 제공
- 메서드 체이닝을 통해 라우팅 처리 가능
- Spring MVC는 api마다 별도 메서드를 생성
코드 설명
- HttpServer.create(): 새로운 HTTP 서버 인스턴스를 생성
- .port(8080): 서버가 바인딩할 포트를 설정
- .route(routes -> {...}): HTTP 경로와 그에 대한 처리 로직을 정의
- .bindNow(): 서버를 지정된 포트에 바인딩하고 즉시 실행
- server.onDispose().block(): 서버가 종료될 때까지 블로킹합니다. 이 라인을 통해 서버는 애플리케이션이 종료되기 전까지 계속 실행
ReactiveAdapterRegistry
- 기본적으로 Mono와 Flux가 핵심 타입이지만, ReactiveAdapterRegistry를 통해 다양한 타입을 지원
- Reactor 뿐만 아니라 RxJava, Mutiny, Coroutine 모두 지원
- ReactiveAdapterRegistry는 다양한 리액티브 타입을 상호 변환할 수 있도록 지원하는 등록소 역할
- ReactiveAdapterRegistry는 내부적으로 각각 라이브러리의 Publisher에 매칭되는 변환함수를 Adatper 형태로 저장하고 getAdapter 메서드를 통해 변환하고자 하는 Publisher를 넘기고 해당 Adatper의 toPublish 메서드를 이용하여 reactive streams의 Publisher로 변경
- Spring WebFlux는 ReactiveAdapterRegistry를 통해 다음과 같은 다양한 리액티브 타입을 처리할 수 있음
- Reactor의 Mono와 Flux
- RxJava의 Single, Observable, Flowable, Maybe
- Kotlin의 Coroutine을 위한 Deferred
- Mutiny의 Uni와 Multi
RxJava Single을 Reactor Mono로 변환하는 예제
HttpHandler
- ServletHttpRequest와 ServletHttpResponse를 인자로 받고 응답을 돌려줘야 하는 시점을 반환하는 함수형 인터페이스
- Http 요청 처리가 끝나면 Mono<Void>를 반환하며 이는 비동기 작업의 완료를 나타냄
- ServletHttpResponse의 setComplete 혹은 writeWith 메서드가 Mono를 반환하므로 그대로 사용하는 케이스가 많음
1. HttpHandler 예제
코드 설명
- handle 메서드는 HTTP 요청을 받아서 처리하고 응답을 생성
- ServerHttpRequest 객체를 사용하여 요청의 쿼리 파라미터를 읽습니다. name 쿼리 파라미터가 있으면 해당 값을 사용하고, 없으면 "world"를 기본값으로 사용하여 응답을 Hello {name}과 같이 반환
- responseBody는 응답 바디를 나타내는 Mono<DataBuffer> 객체
- content.getBytes()를 사용하여 컨텐츠를 바이트 배열로 변환한 후 DataBuffer로 Wrapping
- Hello {name} 텍스트와 함께 name 쿠키를 포함
- ReactorHttpHandlerAdapter를 사용하여 HttpHandler를 어댑터로 Wrapping
- HttpServer.create()를 사용하여 새로운 Netty HTTP 서버를 생성
- channel().closeFuture().sync()는 서버 채널이 닫힐 때까지 블로킹하여 서버가 종료될 때까지 애플리케이션이 실행 상태를 유지
2. HttpHandler의 한계
- request header나 body를 변경하는 작업을 HttpHandler에 직접 구현해야 함
- HttpHandler 내 모든 error를 직접 catch 해서 관리해야 함
- request의 read와 response의 write 모두 HttpHandler에서 구현해야 함
HttpWebHandlerAdapter
- Spring WebFlux에서 HttpHandler의 한계를 보완하고 더욱 향상된 기능을 제공하기 위해 WebHandler 도입
- HttpHandler는 ServerHttpRequest와 ServerHttpResponse를 사용하여 요청과 응답을 처리하는 반면 WebHandler는 ServerWebExchange를 사용하여 요청과 응답을 캡슐화하여 요청과 응답을 하나의 객체로 묶어 좀 보다 편리하게 접근 가능
- WebHandler는 Spring WebFlux의 필터(WebFilter)와 자연스럽게 통합되어 필터는 요청 처리 전에 추가적인 전처리나 응답 처리 후의 후처리를 할 수 있음
- WebHandler는 어노테이션 기반의 프로그래밍 모델과 쉽게 통합될 수 있음 ex) @RequestMapping
- WebHandler 외에도 WebFilter, WebExceptionHandler 등을 지원하는데 모두 ServerWebExchange를 사용하영 요청과 응답 캡슐화
1. ServerWebExchange
- ServerWebExchange는 다음과 같이 요청과 응답 등을 꺼내 사용할 수 있는 메서드를 제공
2. WebHandler 예제 코드
부연 설명
- WebHandler는 ServerWebExchange 객체를 입력으로 받아 Mono<Void>를 반환
- ServerWebExchange는 요청(ServerHttpRequest)과 응답(ServerHttpResponse)을 캡슐화하는 객체
- exchange.getRequest()를 통해 요청 객체를 가져옴
- exchange.getResponse()를 통해 응답 객체를 가져옴
- ResponseBody는 Mono<DataBuffer> 객체로 생성되며 여기서 DataBuffer는 바이트 배열로 wrapping 된 컨텐츠
- 메서드는 비동기적으로 응답을 작성하고 완료될 때 Mono<Void>를 반환
3. WebHttpHandlerBuilder
- WebHandler는 WebHttpHandlerBuilder를 통해 filters, exceptionHandlers, sessionManager 등을 조합한 뒤 HttpWebHandlerAdapter를 생성할 수 있으며 이를 통해 다양한 필터와 예외 처리기를 쉽게 추가하고 구성할 수 있음
- HttpWebHandlerAdapter는 앞서 설명한 HttpHandler를 구현
- Spring WebFlux에서는 WebHandler, WebFilter, WebExceptionHandler를 생성한 뒤 HandlerAdapter를 만들어 Reactor Netty 기반의 서버를 구성
- 여기서 각 컴포넌트들이 통신하는 데이터가 ServerWebExchange
DispatcherHandler
- DispatcherHandler는 Spring WebFlux의 중심 컴포넌트로, HTTP 요청을 받아 적절한 핸들러에 디스패치하는 역할
- SpringMVC의 DispatcherServlet과 유사하게 동작
1. DispatcherHandler 요청 처리 과정
- 사용자가 HTTP 요청을 보내며 해당 요청은 Reactor Netty 서버에 의해 수신
- DispatcherHandler는 수신된 요청을 처리하기 위해 HandlerMapping 리스트에 요청을 보내고 HandlerMapping은 요청 URI와 매핑된 핸들러를 찾기 위해 사용
- HandlerMapping 리스트에서 요청 URI와 일치하는 핸들러를 반환
- DispatcherHandler는 반환된 핸들러를 실행하기 위해 적절한 HandlerAdapter를 요청
- HandlerAdapter 리스트에서 해당 핸들러를 처리할 수 있는 어댑터를 반환
- DispatcherHandler는 적절한 HandlerAdapter를 사용하여 핸들러 실행을 요청
- 핸들러는 요청을 처리하고 HandlerResult를 반환
- HandlerAdapter는 HandlerResult를 DispatcherHandler로 반환
- DispatcherHandler는 HandlerResult를 처리하기 위해 적절한 HandlerResultHandler를 요청하며 HandlerResultHandler는 핸들러의 결과를 최종적으로 클라이언트에 응답으로 변환하는 역할을 수행
- HandlerResultHandler 리스트에서 HandlerResult를 처리할 수 있는 핸들러를 반환
- DispatcherHandler는 적절한 HandlerResultHandler를 사용하여 HandlerResult를 처리하며 해당 과정에서 응답을 생성하고 필요한 후처리를 수행
- 최종적으로 생성된 응답이 DispatcherHandler를 통해 Reactor Netty 서버로 전달된 뒤 사용자에게 응답 전송
- 실제 핸들러에서 반환되는 것은 응답 데이터를 포함하고 있는 Flux 또는 Mono Sequence이기 때문에 메서드 호출을 통해 반환된 Reactor Sequence가 즉시 어떤 작업을 수행하지는 않음
Spring WebFlux의 Non-Blocking 프로세스
- 클라이언트로부터 들어오는 요청을 요청 핸들러가 전달받음
- 전달받은 요청을 이벤트 루프에 push
- 이벤트 루프는 네트워크, DB 연결 작업 등 비용이 드는 작업에 대한 콜백을 등록
- 작업이 완료되면 완료 이벤트를 이벤트 루프에 push
- 등록한 콜백을 호출해 처리 결과를 전달
WebClient
- 동기 blocking 기반의 RestTemplate의 대안으로 비동기 방식의 HTTP 호출을 지원하며 리액티브 스트림을 활용하여 고성능, 확장성, 비동기성 제공
- 다양한 HTTP 메서드를 지원하며, 요청 빌더 패턴을 사용하여 유연하게 요청을 구성할 수 있음
- 응답 처리와 에러 핸들링을 비동기적으로 수행할 수 있어 고성능 애플리케이션을 구축하는 데 유용
- Reactor Netty, Jetty, apache의 HttpComponent를 이용해서 구현
- HTTP 호출을 위한 여러 설정들을 메서드 체이닝을 통해서 설정
1. WebClient GET 요청 예제
2. WebClient POST 요청 예제
3. WebClient 응답 처리 및 에러 핸들링 예제
Spring Security Reactive
Servlet Stack Security 동작 과정은 다음과 같습니다.
- 클라이언트가 HTTP 요청을 보냄
- 요청은 FilterChain을 통해 여러 필터를 거치며 처리되며 각 필터는 doFilter 메서드를 통해 요청과 응답을 조작 가능
- DelegatingFilterProxy는 Spring Security의 핵심 컴포넌트 중 하나로 실제 보안 필터 체인 (FilterChainProxy)에 요청을 위임
- DelegatingFilterProxy는 Spring 애플리케이션 컨텍스트에서 FilterChainProxy 빈을 찾아 요청 처리를 위임
- FilterChainProxy는 여러 SecurityFilterChain을 관리하며, 각 SecurityFilterChain은 특정 경로 패턴과 매칭되는데 요청이 들어오면, FilterChainProxy는 요청 URI와 매칭되는 SecurityFilterChain을 찾음
- 선택된 SecurityFilterChain 내부의 보안 필터들이 순차적으로 실행되며 각 Filter와 Controller에서는 SecurityContextHolder를 사용
- 모든 필터 처리가 완료된 후, 요청은 서블릿으로 전달되며 서블릿은 비즈니스 로직을 처리하고 응답을 생성 및 반환
Servlet Stack Security에서 주목해야 할 컴포넌트는 SecurityContextHolder입니다.
이유는 SecurityContextHolder가 내부적으로 ThreadLocal을 사용하기 때문입니다.
- Spring MVC의 경우 thread-per-request 모델이기 때문에 SecurityContextHolder를 사용해도 무방하지만
- Spring WebFlux의 경우 한 파이프라인 내에서 쓰레드가 계속 변경될 가능성이 높기 때문에 ThreadLocal 대신 context를 사용해야 함
1. SecurityWebFilterChain
- Spring Security Reactive에서는 SecurityWebFilterChain을 사용
- 그리고 SecurityWebFilterChain에서는 SecurityContextHolder 대신 ReactiveSecurityContextHolder를 사용
- ReactiveSecurityContextHolder는 ThreadLocal 대신 SecurityContext를 사용
- Context는 Subscriber와 매핑되기 때문에 구독이 발생할 때마다 해당 구독과 연결된 하나의 Context가 생기며 이 때문에 ThreadLocal 대신 SecurityContext 사용
2. WebFilter
- Spring MVC의 필터처럼 핸들러가 요청을 처리하기 전 전처리 작업을 할 수 있도록 지원
- 주로 보안이나 세션 타임아웃 처리 등 애플리케이션에서 공통으로 필요한 전처리에 사용
- filter(ServerWebExchange exchange, WebFilterChain chain) 메서드로 정의되어 있으며, 파라미터로 전달받은 WebFilterChain을 통해 필터 체인을 형성하여 원하는 만큼의 WebFilter를 추가 가능
3. HandlerFilterFunction
- 함수형 기반의 요청 핸들러에 적용할 수 있는 필터
- filter(ServerRequest request, HandlerFunction<T> next) 메서드로 정의되어 있으며 파라미터로 전달받은 HandlerFunction에 연결됨
- WebFilter의 구현체는 Spring Bean으로 등록되는 반면 HandlerFilterFunction 구현체는 애너테이션 기반의 핸들러가 아닌 함수형 기반의 요청 핸들러에서 함수 형태로 사용되기 때문에 Spring Bean으로 등록되지 않는다는 차이점이 존재
비고
1. Thread 관점에서 Spring MVC vs Spring WebFlux
- Spring MVC는 thread-per-request 모델인 반면 Spring WebFlux는 Reactor Netty의 EventLoop 기반으로 동작
- SpringBoot에서 사용하는 Tomcat 서버의 max thread-pool size는 디폴트로 200
- Spring WebFlux의 worker thread default size는 core 개수
2. request/response 과점에서 Spring MVC vs Spring WebFlux
- Spring MVC에서는 Method Argument로 HttpServletRequest, HttpServletResponse를 사용하는 반면 Spring WebFlux는 ServerHttpRequest, ServerHttpResponse를 사용 혹은 이 둘을 캡슐화한 ServerWebExchange 사용
- Spring MVC에서는 HttpSession을 사용하는 반면 Spring WebFlux에서는 WebSession을 지원
- WebSession은 새로운 Session 생성을 강제하지 않으므로 null이 될 수 있음
- Spring MVC에서 렌더링 목적으로 ModelAndView를 사용하는 반면 Spring WebFlux는 Rendering을 지원
- Spring MVC는 HttpMessageConverter를 사용하는 반면 Spring WebFlux는 HttpMessageWriter를 사용
참고
패스트 캠퍼스 - Spring Webflux 완전 정복 : 코루틴부터 리액티브 MSA 프로젝트까지
스프링으로 시작하는 리액티브 프로그래밍 (황정식 저자)
https://jaehoney.tistory.com/413
'Spring > 스프링으로 시작하는 리액티브 프로그래밍' 카테고리의 다른 글
[Spring WebFlux] 애너테이션 기반 컨트롤러 (0) | 2024.08.20 |
---|---|
[Reactor] 자주 사용되는 Operator (0) | 2024.08.11 |
[Reactor] Reactor 테스트 (0) | 2024.07.31 |
[Reactor] Reactor Sequence 디버깅 방법 (0) | 2024.07.31 |
[Reactor] Context (0) | 2024.07.30 |