Spring WebFlux

Spring WebFlux의 Overview

DevNona 2025. 6. 14. 22:11

Spring WebFlux는 왜 만들어졌나요?

 

그 답의 일부는 적은 수의 스레드로 동시성을 처리하고 더 적은 하드웨어 리소스로 확장할 수 있는 논블로킹 웹 스택이 필요하다는 것입니다. 서블릿의 논블로킹 I/O는 서블릿 API의 나머지 부분들과는 방향이 다릅니다. 왜냐하면 나머지 기능들은 (Filter, Servlet 같은) 동기 방식이거나 (getParameter, getPart 같은) 블로킹 방식이기 때문이다. 이는 모든 논블로킹 런타임에서 기반으로 활용되는 새로운 공통 API에 대한 모티브가 되었습니다. 이는 비동기, 논블로킹 영역에 확고히 자리 잡은 서버(예: Netty) 때문에 중요합니다.

 

답의 다른 부분은 함수형 프로그래밍입니다. Java 5에서 어노테이션을 추가하면서 기회(어노테이션이 달린 REST 컨트롤러나 단위 테스트 등)가 생겼고, Java 8에서 람다 표현식을 추가하면서 Java에서 함수형 API를 만들 수 있는 기회가 생겼습니다. 이는 비동기 로직의 선언적 구성을 허용하는 비차단 애플리케이션과 continuation-style API(Reactive X에서 대중화 됨)에 큰 도움이 됩니다. 프로그래밍 모델 수준에서 Java 8은 Spring WebFlux가 어노테이션이 달린 컨트롤러와 함께 함수형 웹 엔드포인트를 제공할 수 있도록 했습니다.

Define “Reactive”

우리는 "논블로킹"과 "함수형"에 대해 다루었지만 반응형이란 무엇을 의미할까요?

 

"반응형"이라는 용어는 변화에 반응하는 것을 중심으로 구축된 프로그래밍 모델을 말합니다. 즉, 네트워크 구성 요소가 I/O 이벤트에 반응하고, UI 컨트롤러가 마우스 이벤트에 반응하는 등의 방식이 있습니다. 이런 의미에서 논블로킹은 반응형입니다. 왜냐하면 차단되는 대신 이제는 작업이 완료되거나 데이터가 사용 가능해짐에 따라 알림에 반응하는 모드에 있기 때문입니다.

 

Spring 팀에서 "반응형"과 관련하여 중요한 메커니즘 중 하나는 논블로킹 백 프레셔입니다. 동기식 명령형 코드에서 호출 차단은 호출자를 대기 상태로 만드는 자연스러운 형태의 백 프레셔 역할을 합니다. 논블로킹 코드에서는 빠른 생산자가 목적지를 압도하지 않도록 이벤트 속도를 제어하는 것이 중요합니다.

 

Reactive Streams는 백 프레셔를 통한 비동기 구성 요소 간의 상호 작용을 정의하는 작은 사양(Java 9에서도 채택)입니다. 예를 들어, 데이터 저장소(게시자 역할)는 HTTP 서버(구독자 역할)가 응답에 쓸 수 있는 데이터를 생성할 수 있습니다. Reactive Streams의 주요 목적은 구독자가 게시자가 데이터를 얼마나 빨리 또는 얼마나 느리게 생성하는지 제어할 수 있도록 하는 것입니다.

Reactive API

Reactive Streams는 상호 운용성에 중요한 역할을 합니다. 라이브러리와 인프라 구성요소에는 유용하지만 너무 low-level이라 애플리케이션 API로는 유용성이 낮습니다. 애플리케이션에는 비동기 논리를 구성하기 위한 더 높은 수준의 풍부한 함수형 API가 필요합니다. 이는 Java 8 Stream API와 유사하지만 컬렉션에만 국한되지 않습니다. 이것이 반응형 라이브러리의 역할입니다.

 

Reactor는 Spring WebFlux에서 선택한 반응형 라이브러리입니다. ReactiveX 연산자 어휘에 맞춰진 풍부한 연산자 세트를 통해 0..1(Mono) 및 0..N(Flux)의 데이터 시퀀스를 처리하는 Mono 및 Flux API 유형을 제공합니다. Reactor는 Reactive Streams 라이브러리이므로 모든 연산자가 논블로킹 백 프레셔를 지원합니다. Reactor는 서버 측 Java에 중점을 두고 있으며, Spring과 긴밀히 협력하여 개발되었습니다.

 

WebFlux는 Reactor를 핵심 종속성으로 필요로 하지만 Reactive Streams를 통해 다른 반응형 라이브러리와 상호 운용이 가능합니다. 일반적으로 WebFlux API는 일반 Publisher를 입력으로 받아서 내부적으로 Reactor 유형에 맞게 조정하고, 이를 사용하여 Flux 또는 Mono를 출력으로 반환합니다. 따라서 모든 Publisher를 입력으로 전달할 수 있으며 출력에 작업을 적용할 수 있지만 다른 반응형 라이브러리에서 사용하려면 출력을 조정해야 합니다. 가능한 경우(예: 어노테이션이 달린 컨트롤러) WebFlux는 RxJava나 다른 반응형 라이브러리를 사용하여 투명하게 적응합니다. 자세한 내용은 Reactive Libraries를 참조하세요

 NOTE
WebFlux는 Reactive API 외에도 Kotlin의 Coroutines API와 함께 사용할 수 있으며, 이를 통해 더욱 명령형 프로그래밍 스타일을 제공합니다. 다음 Kotlin 코드 샘플은 코루틴 API와 함께 제공됩니다.

Programming Models

spring-web 모듈에는 HTTP 추상화, 지원되는 서버를 위한 Reactive Streams 어댑터, 코덱, Servlet API와 비슷하지만 논블로킹 제약이 있는 핵심 WebHandler API를 포함하여 Spring WebFlux의 기반이 되는 반응형 기반이 포함되어 있습니다.

이러한 기반 위에서 Spring WebFlux는 두 가지 프로그래밍 모델 중에서 선택할 수 있는 옵션을 제공합니다.

  • Annotated Controllers: Spring MVC와 일관성이 있으며 spring-web 모듈의 동일한 어노테이션을 기반으로 합니다. Spring MVC와 WebFlux 컨트롤러는 모두 반응형(Reactor 및 RxJava) 반환 유형을 지원하므로, 이 둘을 구별하기 쉽지 않습니다. 주목할 만한 차이점은 WebFlux가 반응형 @RequestBody 인수도 지원한다는 것입니다.
  • Functional Endpoints: 람다 기반의 가볍고 함수형인 프로그래밍 모델입니다. 이를 애플리케이션이 요청을 라우팅하고 처리하는 데 사용할 수 있는 작은 라이브러리나 유틸리티 세트라고 생각할 수 있습니다. 어노테이션이 달린 컨트롤러와의 가장 큰 차이점은 애플리케이션이 처음부터 끝까지 요청 처리를 담당하는 반면, 어노테이션을 통해 의도를 선언하고 다시 호출받는다는 점입니다.

Applicability

Spring MVC 또는 WebFlux?

 

자연스러운 질문이지만 건전하지 못한 이분법을 만들어내는 질문입니다. 사실, 둘은 함께 작용하여 이용 가능한 옵션의 범위를 확장합니다. 두 가지는 서로의 연속성과 일관성을 유지하도록 설계되었으며, 나란히 사용할 수 있고, 각 측의 피드백은 양쪽 모두에 이롭습니다. 다음 다이어그램은 두 가지가 어떻게 관련되어 있는지, 무엇이 공통점인지, 그리고 각각이 고유하게 지원하는 것이 무엇인지 보여줍니다.

다음과 같은 구체적인 사항을 고려해 보시기 바랍니다.

  • 정상적으로 작동하는 Spring MVC 애플리케이션이 있는 경우 변경할 필요가 없습니다. 명령형 프로그래밍은 코드를 작성하고, 이해하고, 디버깅하는 가장 쉬운 방법입니다. 역사적으로 대부분의 라이브러리가 블로킹 방식이었기 때문에, 라이브러리 선택의 폭이 매우 넓습니다.
  • 이미 논블로킹단 웹 스택을 찾고 있다면 Spring WebFlux는 이 분야의 다른 스택과 동일한 실행 모델 이점을 제공하며, Netty, Tomcat, Jetty, Undertow 및 Servlet 컨테이너와 같은 다양한 서버, 어노테이션이 달린 컨트롤러 및 함수형 웹 엔드포인트와 같은 다양한 프로그래밍 모델, Reactor, RxJava 또는 기타와 같은 다양한 반응형 라이브러리를 선택할 수 있습니다.
  • Java 8 람다 또는 Kotlin과 함께 사용할 수 있는 가볍고 기능적인 웹 프레임워크에 관심이 있다면 Spring WebFlux 기능적 웹 엔드포인트를 사용할 수 있습니다. 이는 더 큰 투명성과 제어로부터 이점을 얻을 수 있는 덜 복잡한 요구 사항을 가진 소규모 애플리케이션이나 마이크로서비스에도 좋은 선택이 될 수 있습니다.
  • 마이크로서비스 아키텍처에서는 Spring MVC 또는 Spring WebFlux 컨트롤러나 Spring WebFlux 기능적 엔드포인트를 사용하여 다양한 애플리케이션을 혼합하여 사용할 수 있습니다. 두 프레임워크 모두에서 동일한 어노테이션 기반 프로그래밍 모델을 지원하므로 지식을 재사용하는 동시에 올바른 작업에 맞는 올바른 도구를 선택하기가 더 쉬워집니다.
  • 애플리케이션을 평가하는 간단한 방법은 종속성을 확인하는 것입니다. 블락킹 퍼시스턴스 API(JPA, JDBC)나 네트워킹 API를 사용해야 하는 경우, 적어도 일반적인 아키텍처에서는 Spring MVC가 가장 좋은 선택입니다. Reactor와 RxJava를 사용하면 별도의 스레드에서 블락킹 호출을 수행하는 것이 기술적으로 가능하지만, 논블락킹 웹 스택의 장점을 최대한 활용할 수는 없습니다.
  • 원격 서비스를 호출하는 Spring MVC 애플리케이션이 있는 경우 반응형 WebClient를 사용해보세요. Spring MVC 컨트롤러 메서드에서 직접 반응형 유형(Reactor, RxJava 또는 기타)을 반환할 수 있습니다. 호출당 지연 시간이나 호출 간 상호 의존성이 클수록 이점은 더욱 극적입니다. Spring MVC 컨트롤러는 다른 반응형 구성 요소도 호출할 수 있습니다.
  • 대규모 팀이 있는 경우, 논블로킹, 함수형, 선언형 프로그래밍으로 전환하는 데 따른 가파른 학습 곡선을 염두에 두십시오. 전체 전환 없이 시작하는 실용적인 방법은 반응형 WebClient를 사용하는 것입니다. 그 이상은 작게 시작해서 그 혜택을 측정해 보세요. 우리는 다양한 앱 분야에서는 이런 변화가 불필요하다고 기대합니다. 어떤 이점을 찾아야 할지 확신이 서지 않는다면, 먼저 논블로킹 I/O가 작동하는 방식(예: 단일 스레드 Node.js의 동시성)과 그 효과에 대해 알아보세요.

Servers

Spring WebFlux는 Tomcat, Jetty, Servlet 컨테이너뿐만 아니라 Netty 및 Undertow와 같은 비 Servlet 런타임에서도 지원됩니다. 모든 서버는 상위 수준의 프로그래밍 모델이 여러 서버에서 지원될 수 있도록 저수준의 공통 API에 맞춰 조정됩니다.

 

Spring WebFlux에는 서버를 시작하거나 중지하는 기본 제공 지원 기능이 없습니다. 하지만 Spring 구성과 WebFlux 인프라를 이용해 애플리케이션을 조립하고 몇 줄의 코드로 실행하는 것은 쉽습니다.

 

Spring Boot에는 이러한 단계를 자동화하는 WebFlux 스타터가 있습니다. 기본적으로 스타터는 Netty를 사용하지만 Maven이나 Gradle 종속성을 변경하면 Tomcat, Jetty 또는 Undertow로 쉽게 전환할 수 있습니다. Spring Boot는 Netty를 기본적으로 사용합니다. Netty는 비동기, 논블로킹 영역에서 더 널리 사용되고 클라이언트와 서버가 리소스를 공유할 수 있기 때문입니다.

 

Tomcat과 Jetty는 Spring MVC와 WebFlux 모두와 함께 사용할 수 있습니다. 하지만 그 사용 방법은 매우 다르다는 점을 명심하세요. Spring MVC는 Servlet 블라킹 I/O에 의존하고 필요한 경우 애플리케이션이 Servlet API를 직접 사용할 수 있도록 합니다. Spring WebFlux는 서블릿 논블로킹 I/O에 의존하며, 저수준 어댑터 뒤에서 서블릿 API를 사용합니다. 직접 사용하도록 노출되지 않습니다.

 

Undertow의 경우, Spring WebFlux는 Servlet API 없이 Undertow API를 직접 사용합니다.

Performance

퍼포먼스는 많은 특징과 의미가 있습니다. 반응형과 논블라킹 방식은 일반적으로 애플리케이션 실행 속도를 높여주지 않습니다. 어떤 경우에는 가능합니다. 예를 들어, WebClient를 사용하여 원격 호출을 병렬로 실행하는 경우가 있습니다. 그러나 논블로킹 방식으로 작업을 수행하려면 더 많은 작업이 필요하며, 그로 인해 필요한 처리 시간이 약간 늘어날 수 있습니다.

 

반응형 및 논블로킹의 주요 기대 이점은 적은 수의 고정된 스레드와 적은 메모리로 확장할 수 있는 기능입니다. 이를 통해 애플리케이션은 더 예측 가능한 방식으로 확장되므로 부하에 대한 복원력이 향상됩니다. 하지만 이러한 이점을 얻으려면 어느 정도의 지연 시간(느리고 예측할 수 없는 네트워크 I/O가 혼합된 것 포함)이 필요합니다. 이것이 바로 반응형 스택의 강점이 드러나기 시작하는 부분이며, 그 차이점은 엄청날 수 있습니다.

Concurrency Model

Spring MVC와 Spring WebFlux는 모두 어노테이션이 달린 컨트롤러를 지원하지만, 동시성 모델과 블로킹 및 스레드에 대한 기본 가정에 중요한 차이점이 있습니다.

 

Spring MVC(및 일반적인 서블릿 애플리케이션)에서는 애플리케이션이 현재 스레드를 차단할 수 있다고 가정합니다(예: 원격 호출의 경우). 이러한 이유로 서블릿 컨테이너는 요청을 처리하는 동안 잠재적인 차단을 흡수하기 위해 대규모 스레드 풀을 사용합니다.

Spring WebFlux(및 일반적인 논블로킹 서버)에서는 애플리케이션이 차단되지 않는다고 가정합니다. 따라서 논블라킹 서버는 작고 고정된 크기의 스레드 풀(이벤트 루프 워커)을 사용하여 요청을 처리합니다.

Invoking a Blocking API

블라킹 라이브러리를 사용해야 하는 경우는 어떻게 되나요? Reactor와 RxJava는 둘 다 다른 스레드에서 처리를 계속하기 위해 publishOn 연산자를 제공합니다. 즉, 쉽게 탈출할 수 있는 방법이 있다는 뜻입니다. 하지만 블라킹 API는 이 동시성 모델에 적합하지 않다는 점을 명심하세요.

Mutable State

Reactor와 RxJava에서는 연산자를 통해 논리를 선언합니다. 런타임에는 데이터가 순차적으로, 각기 다른 단계로 처리되는 반응형 파이프라인이 형성됩니다. 이것의 주요 이점은 파이프라인 내의 애플리케이션 코드가 동시에 호출되지 않기 때문에 애플리케이션이 변경 가능한 상태를 보호할 필요가 없다는 것입니다.

Threading Model

Spring WebFlux를 실행하는 서버에서 어떤 스레드가 나타날 것으로 예상하시나요?

  • "바닐라" Spring WebFlux 서버(예: 데이터 접근이나 기타 선택적 종속성이 없는 경우)에서는 서버를 위한 스레드 하나와 요청 처리를 위한 스레드 여러 개(일반적으로 CPU 코어 수만큼)를 기대할 수 있습니다. 그러나 서블릿 컨테이너는 서블릿(블로킹) I/O와 서블릿 3.1(논블로킹) I/O 사용을 모두 지원하기 위해 더 많은 스레드(예: Tomcat의 경우 10개)로 시작할 수 있습니다.
  • 반응형 WebClient는 이벤트 루프 스타일로 작동합니다. 따라서 이와 관련된 처리 스레드의 수가 적고 고정되어 있음을 볼 수 있습니다(예: Reactor Netty 커넥터가 있는 react-http-nio-). 하지만 Reactor Netty를 클라이언트와 서버 모두에 사용하는 경우, 기본적으로 둘은 이벤트 루프 리소스를 공유합니다.
  • Reactor와 RxJava는 publishOn 연산자와 함께 사용할 스케줄러라는 스레드 풀 추상화를 제공하며, 이 연산자는 다른 스레드 풀로 처리를 전환하는 데 사용됩니다. 스케줄러의 이름은 특정 동시성 전략을 암시합니다. 예를 들어, "parallel"(제한된 수의 스레드가 있는 CPU 중심 작업의 경우) 또는 "elastic"(많은 수의 스레드가 있는 I/O 중심 작업의 경우)입니다. 이러한 스레드가 보인다면 일부 코드에서 특정 스레드 풀 Scheduler 전략을 사용하고 있다는 의미입니다.
  • 데이터 액세스 라이브러리와 기타 타사 종속성도 자체 스레드를 생성하고 사용할 수 있습니다.

Configuring

Spring Framework는 서버 시작 및 중지에 대한 지원을 제공하지 않습니다. 서버의 스레딩 모델을 구성하려면 서버별 구성 API를 사용해야 하며, Spring Boot를 사용하는 경우 각 서버에 대한 Spring Boot 구성 옵션을 확인해야 합니다. WebClient를 직접 구성할 수 있습니다. 다른 모든 라이브러리에 대한 자세한 내용은 해당 설명서를 참조하세요.