Spring Web Reactive | 1. Spring WebFlux | 1.1. 개요

Spring WebFlux가 만들어진 이유는 무엇인가?

그 이유중 하나는 적은 스레드로 동시성을 처리하고, 보다 적은 하드웨어 자원으로 확장하기 위해, 논블러킹(non-blocking) Web 스택이 필요했기 때문이다. Servlet 3.1에서는 논블러킹 I/O API를 제공하였지만, 이를 사용하면 계약이 동기화(Filter, Servlet) 또는 블로킹(getParameter, getPart)인 서블릿 API와는 멀어지게 된다. 이런 점이 새로운 공통 API가 블로킹 런타임 전체의 기반으로서 기능하게 하는 동기가 되었다. 비동기 논블러킹 환경에서 잘 확립되어 있는 서버(Netty 등)이 있기 때문에, 이는 중요하다.

또 다른 이유는 함수형 프로그래밍이다. Java 5에 어노테이션의 등장으로 새로운 가능성(어노테이션 REST 컨트롤러 및 단위 테스트 등)이 열렸고, Java 8에 람다 식을 추가 되므로써 Java의 함수 API의 가능성을 열리게 되었다 . 이것은 비동기 로직의 선언적인 합성을 가능하게 하는 논블로킹 응용 프로그램과 연속 전달 스타일 API(CompletableFutureReactiveX에 의해 대중화 됨)에 유용하다. 프로그래밍 모델 레벨에서는 Java 8에 의해 Spring WebFlux은 어노테이션이 선언된 컨트롤러와 함께 함수형 Web 엔드 포인트를 제공 할 수 있게 되었다.

1.1.1. “리액티브(Reactive)” 정의

“non-blocking"과 “함수형(functional)“에 대해 언급하였는데, 리액티브(reactive)란 무엇인가?

“리액티브"라는 용어는 변화에 대한 반응에 따라 구성되는 프로그래밍 모델을 말한다. 네트워크 컨포넌트는 I/O 이벤트에 반응하고 UI 컨트롤러는 마우스 이벤트에 반응한다. 그런 의미에서 논블로킹은 리액티브이다. 왜냐하면 블로킹하는 대신에 작업이 완료되거나 데이터가 이용 가능하게 되었다는 알림에 반응하는 모드로 되어 있기 때문이다.

Spring 팀이 “리액티브"에 연관 또 다른 중요한 메커니즘은 논블로킹 역압력(back pressure)이다. 동기화 명령형 코드에서 블로킹 호출은 호출하는 측을 대기하도록 하는 자연스러운 형태의 역 압력의 역할을 한다. 논블로킹 코드에서는 빠른 생성자(producer)가 그 수신을 압도하지 않도록 이벤트의 속도를 제어하는 것이 중요하다.

Reactive Streams는 역압력이 있는 비동기 구성 요소 간의 상호 작용을 정의하는 간단한 스택(Java 9에서도 채택)이다. 예를 들어, 데이터 저장소(구독자:Publisher 역할)는 HTTP 서버(게시자:Subscriber 역할)이 응답에 쓸 수 있는 데이터를 생성 할 수 있다. Reactive Streams의 주요 목적은 구독자가 게시자에게 데이터를 생성하는 속도와 속도를 제어 할 수 있도록 하는 것이다.

자주 묻는 질문 : 게시자가 속도를 늦출 수 없다면 어떻게 되나?
Reactive Streams의 목적은 메커니즘과 경계를 설정할 뿐이다. 게시자가 속도를 늦출 수 없다면 버퍼를 사용하던지 드롭하거나 실패할지 결정해야 한다.

1.1.2. 리액티브(Reactive) API

Reactive Streams는 상호 운용성에 중요한 역할을 한다. 라이브러리 및 인프라 구성 요소에 대한 관심은 있지만, 응용 프로그램 API로는 낮은 수준도 있기 때문에 유용하지 않다. 응용 프로그램은 Java 8 Stream API와 비슷하지만 컬렉션뿐만 아니라 비동기 로직을 구성하기 위해 더 높은 수준에서 더 풍부한 API가 필요하다. 이것이 리액티브 라이브러리가 하는 역할이다.

Reactor는 Spring WebFlux에 채택된 리액티브 라이브러리이다. ReactiveX vocabulary of operators에 맞춘 다양한 연산자 세트를 통해 0..1 (Mono), 0..N (Flux)의 데이터 시퀀스에서 작동 MonoFlux API 유형을 제공한다. Reactor는 Reactive Streams 라이브러리이므로 모든 오퍼레이터가 논블로킹 역압력을 지원한다. Reactor는 서버 측 Java에 중점을 두고 있다. Spring과의 긴밀한 협력으로 개발된다.

WebFlux 핵심 의존으로 Reactor를 필요로 하지만, Reactive Streams를 통해 다른 리액티브 라이브러리와 상호 운용 할 수 있다. 일반적인 규칙으로 WebFlux API는 입력으로 일반 게시자를 받아들이고 그것을 내부에서 Reactor 유형에 맞게 조정하고, 이를 사용하여 출력으로 Flux 또는 Mono를 반환한다. 따라서 모든 게시자를 입력으로 전달할 수 출력에 작업을 적용 할 수 있지만, 다른 리액티브 라이브러리에서 사용하기 위해 출력을 조정해야 한다. 가능하면 언제든지 (예를 들어, 어노테이션이 선언된 컨트롤러) WebFlux는 RxJava 또는 다른 리액티브 라이브러리의 사용에 투명하게 적응한다. 자세한 내용은 리액티브 라이브러리를 참조해라.

Reactive API 이외에 WebFlux는 Kotlin의 Coroutines API에서도 사용할 수 있고, 더 긴급한 스타일의 프로그래밍을 제공한다. 다음 Kotlin 코드 예제는 Coroutines API에서 제공된다.

1.1.3. 프로그래밍 모델

spring-web 모듈은 HTTP 추상화를 포함 Spring WebFlux 지원되는 서버에 대한 Reactive Streams 어댑터 , 코덱 및 서블릿 API에 필적하지만 블로킹 계약을 포함한 코어 WebHandler API의 기초가 되는 리액티브 기반이 포함되어 있다.

그 기반에서 Spring WebFlux는 2개의 프로그래밍 모델의 대안을 제공한다.

  • 어노테이션이 선언된 컨트롤러 : Spring MVC와 일치하고 spring-web 모듈에서 같은 어노테이션을 기반으로 한다. Spring MVC 컨트롤러와 WebFlux 컨트롤러는 모두 리액티브 (Reactor 및 RxJava) 반환 형식을 지원하고 있기 때문에 구분하기는 쉽지 않다. 주목할 만한 차이점 중 하나는 WebFlux가 리액티브 @RequestBody 인수도 지원한다는 것이다.

  • 함수 엔드 포인트 : 람다 기반의 가볍고 함수 프로그래밍 모델. 이것은 응용 프로그램이 요청 라우팅 및 처리에 사용할 수 있는 작은 라이브러리 또는 유틸리티의 집합으로 생각할 수 있다. 어노테이션이 선언된 컨트롤러와의 가장 큰 차이점은 응용 프로그램이 처음부터 끝까지의 요청 처리를 담당하는 것으로, 어노테이션을 통해 인텐트를 선언하고 콜백을 하는 것이다.

1.1.4. 적용성

Spring MVC 또는 WebFlux?

당연한 질문이지만, 애매 모호한 이분법적인 질문이다. 사실, 두개 모두 같이 사용되므로 사용 가능한 옵션의 범위가 넓어진 것이다. 이 두개는 서로 연속성과 일관성을 유지하도록 설계되어 있으며, 동시에 사용할 수 있다. 또한, 양방의 피드백은 모두에게 이득을 제공한다. 다음 그림은 이 2개가 어느 것과 관련 있거나 공통되는 것으로 각각 고유하게 지원하는 것을 보여준다.

spring-mvc-and-webflux-venn

다음의 특정 사항을 고려하도록 하자.

  • 제대로 작동하는 Spring MVC 응용 프로그램이 있는 경우 변경할 필요가 없다. 명령형 프로그래밍은 코드를 작성, 이해 및 디버깅하는 가장 쉬운 방법이다. 지금까지 대부분은 블로킹으로 되어 때문에 라이브러리의 선택은 최대로 할 수 있다.

  • 이미 논블로킹 Web 스택을 알아보고 있다면, Spring WebFlux는 이 분야의 다른 사용자와 동일한 실행 모델의 장점을 제공하고 서버 (Netty, Tomcat, Jetty, Undertow, Servlet 3.1+ 컨테이너) 옵션 제공한다. 프로그래밍 모델 (어노테이션이 선언된 컨트롤러로 작동하는 Web 엔드 포인트) 및 리액티브 라이브러리 (Reactor, RxJava 또는 기타)를 선택한다.

  • Java 8 람다 또는 Kotlin에서 사용하는 경량 함수 Web 프레임워크에 관심이 있다면, Spring WebFlux 함수 Web 엔드 포인트를 사용할 수 있다. 또한 투명성 및 제어성을 높임으로써 혜택을 얻을 복잡한 요구 사항이 없는 소규모 어플리케이션과 마이크로 서비스에 적합하다.

  • 마이크로 서비스 아키텍처에서는 Spring MVC 또는 Spring WebFlux 컨트롤러 또는 Spring WebFlux 함수 엔드 포인트 중 하나의 애플리케이션을 혼합 할 수 있다. 두 프레임워크에서 같은 어노테이션 기반의 프로그래밍 모델을 지원하고 있기 때문에 적절한 작업에 적합한 도구를 선택하면서 지식을 재사용하기 쉬워진다.

  • 응용 프로그램을 평가하는 간단한 방법은 종속성을 확인하는 것이다. 사용 차단 지속성 API (JPA, JDBC) 또는 네트워크 API가 있다면 Spring MVC는 적어도 일반적인 아키텍처에 적합하다. Reactor와 RxJava 모두에서 다른 스레드에서 차단 호출을 수행하는 것은 기술적으로는 가능하지만, 논블로킹 Web 스택을 최대한 활용할 수 없다.

  • 원격 서비스를 호출 Spring MVC 응용 프로그램이 있는 경우는 사후 WebClient를 사용해 보길 바란다. Spring MVC 컨트롤러 메서드에서 직접 리액티브 형 (Reactor, RxJava 또는 기타)을 반환 할 수 있다. 호출마다 지연 또는 호출 간의 상호 의존성이 높을 수록 더 효과적이다. Spring MVC 컨트롤러는 다른 리액티브 컴포넌트를 호출 할 수 있다.

  • 팀의 규모가 크다면, 논블로킹, 함수형, 선언적 프로그래밍으로 전환의 러닝 커브에 가파르다는 것에 유의해야 한다. 전체를 변경하지 않고 시작하는 실용적인 방법은 리액티브 WebClient부터 사용하는 것이다. 그것을 넘게 되면 작은 것부터 시작해서, 이로 얻은 이점을 측정한다. 대부분의 응용 프로그램의 경우 이 전환이 꼭 필요하다라고 생각되지 않는다. 얻고자 하는 장점을 무엇인지 모른다면, non-blocking I/O 구조(예를 들어, 단일 스레드 Node.js의 동시 실행)와 그 어떻게 동작하는지에 대해 학습부터 시작보길 바란다.

1.1.5. 서버(Servers)

Spring WebFlux은 Tomcat, Jetty, Servlet 3.1+ 컨테이너 및 Netty와 Undertow 등의 서블릿이 아닌 런타임에서 지원된다. 모든 서버는 낮은 수준의 공통 API를 준수하는 서버 사이에서 높은 수준의 프로그래밍 모델을 지원한다.

Spring WebFlux 서버를 시작 또는 중지하기 위한 기본 내장되지 있지 않는다. 그러나 Spring 설정과 WebFlux 인프라에서 애플리케이션을 조립하여 몇 줄의 코드로 실행하는 것은 간단한다.

Spring Boot는 이러한 단계를 자동화하는 WebFlux 스타터가 있다. 기본적으로 초보 Netty를 사용하지만, Maven 또는 Gradle의 종속성을 변경하는 것으로, Tomcat, Jetty 또는 Undertow으로 쉽게 전환 할 수 있다. Spring Boot는 기본적으로 Netty로 설정되어 있다. 이는 비동기 논블로킹 공간에서 더 널리 사용되어 클라이언트와 서버가 자원을 공유 할 수 있기 때문이다.

Tomcat과 Jetty는 Spring MVC와 WebFlux 모두에서 사용할 수 있다. 그러나 동작 방식은 크게 다르다는 점에 유의해야 한다. Spring MVC는 서블릿 블로킹 I/O에 의존하고 있으며, 필요에 따라 응용 프로그램이 서블릿 API를 직접 사용할 수 있도록 한다. Spring WebFlux는 Servlet 3.1 non-blocking I/O 기반으로 낮은 수준 어댑터 뒤단에서 서블릿 API를 사용위해 직접적으로 노출되어 있지는 않는다.

Undertow의 경우, Spring WebFlux 서블릿 API없이 Undertow API를 직접 사용한다.

1.1.6. 성능(Performance)

성능에 많은 특성과 의미가 있다. 일반적으로 리액티브 논블로킹 응용 프로그램의 실행 속도가 향상되지 않는다. 경우에 따라 (예를 들어, WebClient를 사용하여 원격 호출을 병렬로 실행하는 경우) 전체적으로 논블로킹 방법을 실행하려면 더 많은 작업이 필요하며, 필요한 처리 시간을 약간 증가 시킬 수도 있다.

리액티브 논블로킹의 주요 예상 이점은 고정된 적은 수의 스레드와 적은 메모리로 확장 할 수 있다. 예측 가능한 방식으로 확장되기 때문에 부하가 걸린 경우에도 응용 프로그램의 탄력성이 높아진다. 그러나 이러한 장점을 관찰하려면 어느 정도의 지연 (저속으로 예측할 수 없는 네트워크 I/O의 혼합을 포함)이 필요하다. 그것이 리액티브 스택의 강점을 볼 수 있으며, 그 차이는 크다.

1.1.7. 동시성 모델(Concurrency Model)

Spring MVC와 Spring WebFlux 모두 어노테이션이 선언된 컨트롤러를 지원하지만, 동시성 모델과 블로킹과 스레드의 기본 가정에 중요한 차이가 있다.

Spring MVC (및 서블릿 애플리케이션 전반)에는 응용 프로그램이 현재 스레드를 차단 할 수 있다고 가정한다(예를 들어, 원격 호출의 경우). 따라서 서블릿 컨테이너는 큰 스레드 풀을 사용하여, 요청을 처리하는 동안 잠재적인 블로킹을 흡수한다.

Spring WebFlux (및 일반적으로 non-blocking 서버)에서 응용 프로그램이 차단되지 않는다고 가정한다. 따라서 non-blocking 서버는 작은 고정 크기의 스레드 풀(이벤트 루프 작업자)를 사용하여 요청을 처리한다.

“스케일링"과 “적은 스레드"는 모순처럼 들릴지도 모르지만 현재 스레드를 차단하는 (및 대신 콜백에 의존하는) 것은 흡수 차단 호출이 없기 때문에 여분의 스레드가 필요 없다는 것을 의미한다.

블로킹 API 호출

블로킹 라이브러리를 사용할 필요가 있다면 어떻게 될까? Reactor와 RxJava는 모두 다른 스레드에서 처리를 계속하기 위해 publishOn 운영자를 제공한다. 즉, 간단히 피해갈 방법은 있지만, 블로킹 API는 이 동시 모델에는 적합하지 않다.

가변 상태(Mutable State)

Reactor 및 RxJava에서는 연산자를 사용하여 논리를 선언한다. 실행시 리액티브 파이프 라인이 형성되어 데이터가 다른 스테이지에서 순차적으로 처리된다. 이것의 가장 큰 장점은 파이프 라인의 응용 프로그램 코드가 동시에 호출 할 수 없기 때문에 응용 프로그램이 변경 가능한 상태를 보호 할 필요가 없는 것이다.

스레드 모델(Threading Model)

Spring WebFlux에서 실행되는 서버에서 어떤 스레드를 표시해야 하나?

  • “바닐라” Spring WebFlux 서버 (예를 들어, 데이터 액세스 및 기타 옵션 종속성 없음)는 서버에 하나의 스레드와 요청 처리를 위한 몇 가지 다른 스레드 (일반적으로 CPU 코어의 수)를 기대 수 있다. 그러나 서블릿 (블로킹) I/O와 서블릿 3.1 (non-blocking) I/O 모두 사용을 지원하는 서블릿 컨테이너는 더 많은 스레드 (예를 들어, Tomcat 10)에서 시작하는 경우가 있다.

  • 리액티브 WebClient는 이벤트 루프 스타일로 작동한다. 이와 관련된 몇 가지 고정 된 수의 처리 스레드 (예를 들어, Reactor Netty 커넥터가있는 reactor-http-nio-)을 확인할 수 있다. 그러나 Reactor Netty가 클라이언트와 서버 모두에 사용되는 경우, 2 개는 기본적으로 이벤트 루프 자원을 공유한다.

  • Reactor 및 RxJava 스케줄러라는 스레드 풀의 추상화를 제공하고, 처리를 다른 스레드 풀로 전환하는데 사용되는 publishOn 연산자와 함께 사용한다. 스케줄러는 특정 동시성 전략을 제안하는 이름이 있다. 예를 들어, “병렬(parallel)"(제한된 수의 스레드를 사용하는 CPU 바운드 작업의 경우) 또는 “엘라스틱(elastic)"(다수의 스레드를 사용하는 I/O 바운드 작업의 경우) 이러한 스레드가 표시되는 경우 일부 코드가 특정 스레드 풀 Scheduler 전략을 사용하는 것을 의미한다.

  • 데이터 액세스 라이브러리 및 기타 타사 종속성도 자신의 스레드를 생성하여 사용할 수 있다.

설정

Spring Framework는 서버 시작 및 중지를 지원하지 않는다. 서버의 스레드 모델을 구성하려면 서버 별 구성 API를 사용하거나 Spring Boot를 사용하는 경우 각 서버의 Spring Boot 구성 옵션을 확인해야 한다. WebClient를 직접 구성 할 수 있다. 다른 모든 라이브러리에 대해서는 각각의 문서를 참조해라.




최종 수정 : 2021-04-12