Spring Framework
Spring Webflux - Overview
흥부가귀막혀
2022. 3. 14. 12:36
Spring Webflux 란?
- Spring 5.0 버전부터 추가된 리액티브 스택 웹 프레임워크다.
- 완벽하게 non-blocking 으로 동작하며, Reactive Streams back pressure를 지원하고, Netty, Undertow, 서블릿 3.1+ 컨테이너 서버에서 실행된다.
- 기존 Spring MVC 와 같이 Spring Framework 에 공존하고 MVC 를 사용하며 Webflux 개발을 하는것도 가능하다.
Spring Webflux 는 왜 만들어졌나?
- 적은 쓰레드로 동시 처리를 제어하고 적은 하드웨어 리소스로 확장하기 위한 논블로킹 웹 스택의 필요성이 대두됨.
- 이전에도 서블릿 3.1 은 논블로킹 I/O를 위한 API를 제공했지만, 서블릿으로 논블로킹을 구현하려면 다른 동기 처리나(Filter, Servlet) 블로킹 방식(getParameter, getPart) 을 쓰는 API를 피해야 하지만 그렇게 하기가 어렵다. 이런 점 때문에 어떤 논블로킹과도 잘 동작하는 새 공통 API를 만들게 되었다. 이미 비동기 논블로킹 환경에서 자리를 잡은 서버(e.g. Netty) 때문에라도 새 API가 필요했다.
- 또 다른 이유로는 함수형 프로그래밍이 있다. 자바 5의 어노테이션 등장으로 선택의 폭이 넓어진 것처럼(어노테이션을 선언한 REST 컨트롤러나 유닛 테스트 등), 자바 8에서 추가된 람다 표현식덕분에 자바에서도 함수형 API를 작성할 수 있게 됐다. 이 기능은 논블로킹 어플리케이션을 만들 때도 요긴하게 쓰이며, 이제는 continuation-style API(CompletableFuture 와 ReactiveX 로 대중화된)로 비동기 로직을 선언적으로 작성할 수 있다. 프로그래밍 모델 관점에서 보면, 웹플럭스에서 어노테이션을 선언한 컨트롤러와 더불어 함수형 웹 엔드포인트를 사용할 수 있는 건 자바 8 덕분이다.
“리액티브” 는 무슨 뜻인가?
- “리액티브”라는 용어는 변화에 반응하는 것을 중심에 두고 만든 프로그래밍 모델을 의미한다 (I/O 이벤트에 반응하는 네트워크 컴포넌트, 마우스 이벤트에 반응하는 UI 컨트롤러 등). 논블로킹은 작업을 기다리기보단 완료되거나 데이터를 사용할 수 있게 되면 반응하므로, 이 말대로면 논블로킹도 리액티브다.
- 스프링은 “리액티브”와 관련한 중요한 메커니즘이 하나 더 있는데, 논블로킹 back pressure다. 동기식 명령형(imperative) 코드에서 블로킹 호출은 호출자를 강제로 기다리게 하는 일종의 back pressure다. 논블로킹 코드에선, 프로듀셔 속도가 컨슈머 속도를 압도하지 않도록 이벤트 속도를 제어한다.
- 리액티브 스트림은 back pressure를 통한 비동기 컴포넌트 간의 상호작용을 정의한 간단한 스펙이다(자바 9에서도 채택했다). 예를 들어 데이터 레포지토리(Publisher 역할)가 데이터를 만들고, HTTP 서버(Subscriber 역할)로 이 데이터로 요청을 처리할 수 있다. 리액티브 스트림을 쓰는 주목적은 subscriber가 publisher의 데이터 생산 속도를 제어하는 것이다.
Reactive API
- 리액티브 스트림은 컴포넌트 상호 작용에서 중요한 역할을 한다. 하지만 이건 라이브러리와 기반 구조에 사용되는 컴포넌트엔 유용해도, 어플리케이션 API에서 다루기엔 너무 저수준이다. 어플리케이션은 비동기 로직을 만들기 위한 풍부한 고수준 함수형 API가 필요하다 (자바 8 스트림 API와 비슷하지만 컬렉션만을 위한 게 아니다). 이게 바로 리액티브 라이브러리가 하는 일이다.
- Reactor는 스프링 웹플럭스가 선택한 리액티브 라이브러리다. 리액터는 Mono 와 Flux API 타입을 제공한다.
- ReactiveX vocabulary of operators 에 정리된 풍부한 연산자를 사용해 데이터 시퀀스를 0~1개는 Mono, 0~N개는 Flux로 표현할 수 있다. 리액터는 리액티브 스트림 라이브러리이기 때문에 모든 연산자는 논블로킹 back pressure를 지원한다. 리액터는 특히 서버 사이드 자바에 초점을 두고 스프링과 긴밀히 협력해서 개발됐다.
- 웹플럭스는 Reactor 를 핵심 라이브러리로 사용하지만, 다른 리액티브 라이브러리를 써도 리액티브 스트림으로 상호작용할 수 있다. 웹플럭스 API의 일반적인 룰은, 순수한 Publisher를 입력으로 받아 내부적으로 리액터 타입으로 맞추고, 이걸 사용해서 Flux나 Mono를 반환한다. 따라서 어떤 Publisher든 입력으로 전달하고 연산할 수 있지만, 다른 리액티브 라이브러리를 사용하려면 출력 형식을 맞춰줘야 한다. 웹플럭스는 가능만 하다면 (e.g. 어노테이션을 선언한 컨트롤러) 투명한 방식으로 RxJava나 다른 리액티브 라이브러리에 맞게 바꿔준다. 자세한 내용은 Reactive Libraries를 참고.
❗ 리액티브 API와는 별개로 웹플럭스는 코틀린의 코루틴 API와도 사용할 수 있는데, 이를 사용하면 좀 더 명령적(imperative)인 프로그래밍이 가능하다.
Concurrency Model
- 스프링 MVC와 스프링 웹플럭스 둘 다 annotated controller를 사용할 수 있다는 점은 동일해도, 동시성 모델과 블로킹/쓰레드 기본 전략이 다르다.
- 스프링 MVC는 (그리고 일반적인 서블릿 어플리케이션이라면) 어플리케이션이 처리 중인 쓰레드가 잠시 중단될 수 있다(예를 들어 외부 서비스를 호출하면). 그렇기 때문에 서블릿 컨테이너는 이 블로킹을 대비에 큰 쓰레드 풀로 요청을 처리한다.
- 스프링 웹플럭스는 (그리고 일반적인 논블로킹 서버라면) 실행 중인 쓰레드가 중단되지 않는다는 전제가 있다. 따라서 논블로킹 서버는 작은 쓰레드 풀(이벤트 루프 워커)을 고정해놓고 요청을 처리한다.
- Thread 는 Event 가 들어온 순서대로 로직들을 처리하게 되며 요청 들어온 Event 가 각 Thread 에 골고루 분산되어 효율적으로 비지니스 로직이 실행되게 된다. 필요한 task 작업이 동시에 수행되는건 아니지만 Worker Thread 에 여유가 있다면 이에 준하는 성능으로 비지니스 로직이 동작하게 된다.