[번역] Spring WebFlux (1)

rin·2020년 9월 14일
4

Document 번역

목록 보기
20/22
post-thumbnail

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux 를 번역합니다.

❗️ Spring WebFlux
Spring Framework에 포함된 오리지날 웹 프레임워크인 Spring Web MVC는 Servlet API 및 Servlet 컨테이너를 위해 특별히 제작되었다. 반응형-스택 웹 프레임워크인 Spring WebFlux는 5.0버전부터 추가되었다. Spring WebFlux는 non-blocking이며 Reactive Streams back pressure를 지원하고 Netty, Undertow, Servlet 3.1+ 컨테이너와 같은 서버에서 실행된다.

Overview

Spring WebFlux가 만들어진 이유

적은 수의 스레드로 동시 처리를 가능하게 하고 더 적은 하드웨어 리소스를 가지고 확장 가능한 non-blocking web stack이 필요하기 때문이다. Servlet 3.1에서 non-blocking I/O를 위한 API를 제공했으나 contracts가 동기식(Filter, Servlet)이거나 blocking(getParameter, getPart)이기 때문에 이를 사용하는 것은 Servlet API의 나머지 부분들과의 괴리를 만들었다. 이로인해 새로운 공통 API가 non-blocking runtime의 기반이 되었으며 이는 async, non-blocking 공간에 잘 구축된 서버(e.g. Netty) 때문에 중요하다.

두 번째는 함수형 프로그래밍이다. Java 5에 어노테이션을 추가함으로써 어노테이션으로 이뤄진 REST controller나 unit test를 구성한 기회가 생긴 것처럼 Java 8에 람다식을 추가하면 Java의 함수형 API를 구성할 기회가 생겼다. 이는 비동기 로직의 선언형 구성을 허용하는 non-blocking 어플리케이션과 continuation-style API(CompletableFutureReactiveX에서 널리 사용됨)에게 유익하다. 프로그래밍 모델 수준에서, Java 8은 Spring WebFlux가 어노테이션이 달린 컨트롤러와 함께 함수형 웹 엔드포인트를 제공하도록 만들었다.

"Reactive"의 정의

'non-blocking'과 'functional'이란 단어를 언급했는데, 'reactive'는 무슨 의미일까?

'reactive'라는 용어는 I/O 이벤트에 반응하는 네트워크 컴포넌트, 마우스 이벤트에 반응하는 UI 컨트롤러 등과 같은 변화에 대한 반응을 중심으로 구축된 프로그래밍 모델을 나타낸다. 따라서 non-blocking은 blocking과 다르게 작업이 완료되거나 데이터를 사용가능한 상태가 되면 알림에 반응하는 방식이 있으므로 reactive하다고 말 할 수 있다.

'non-blocking back pressure' 또한 'reactive'와 연관되는 중요한 메커니즘이다. 동기식 명령형 코드에서 blocking call은 호출자가 대기하기 위한 back pressure 역할을 한다. non-blocking 코드에서는 빠른 생산자가 목적지를 압도하지 않도록 이벤트 속도를 제어하는 것이 중요하다.

Reactive Streams는 back pressure가 있는 비동기 컴포넌트간의 상호 작용을 정의하는 소규모 사양이다. (이는 Java 9에서도 채택되었다.) 예를 들어 (Publisher) 데이터 레파지토리는 (Subscriber) HTTP 서버가 응답에 사용할 데이터를 생성할 수 있다. Reactive Streams의 주된 목적은 Subscriber가 Publisher의 데이터 생성 속도(빠르게/느리게)를 제어 할 수 있도록 하는 것이다.

Publisher가 속도를 늦출 수 없는 경우에는..?
Reactive Streams의 목적은 메커니즘과 경계를 설정하는 것이다. 따라서 Publisher가 속도를 늦출 수 없다면 버퍼 사용, 제거, 실패 여부를 결정해야한다.

Reactive API

Reactive Streams는 상호 운용성을 위해 중요한 역할을 한다. 라이브러리와 인프라 컴포넌트와 관련은 있으나 너무 낮은 수준이기 때문에 어플리케이션 API로써는 유용성이 떨어진다. 어플리케이션은 비동기 로직을 구성하기 위해 더 높은 수준의 풍부한 함수형 API가 필요하다. (이는 Java 8의 Stream API와 유사하나 컬렉션에만 해당되는 것이 아니다.) 이것이 reactive 라이브러리의 역할이다.

Reactor는 Spring WebFlux에서 선택한 reactive library이다. 이는 ReactiveX 어휘 연산자와 일치하는 풍부한 연산자를 통해 0..1(Mono)과 0..N(Flux)의 데이터 시퀀스에서 작업하기 위한 MonoFlux API 유형을 제공한다. Reactor는 Reactive Streams 라이브러리이므로 모든 연산자는 non-blocking back pressure 지원한다. Reactor는 servier-side java에 큰 비중을 두며 Spring과 협력적으로 개발되었다.

WebFlux는 Reactor에 핵심 종속성을 가지고 있지만 Reactive Stream를 통해 다른 reactive 라이브러리와 상호 운용 할 수 있다. 일반적으로 WebFlux API 일반적인 Publisher를 입력으로 받아들이고 이를 내부적으로 Reactor 타입에 맞도록 변형(조정)하여 사용하며 FluxMono를 출력으로 반환한다. 따라서 어떤 Publisher든 입력으로 전달할 수 있고 출력으로 연상을 적용할 수 있으나, 다른 reactive 라이브러리와 사용하기 위해서는 출력을 조정해야한다. 어노테이션으로 이뤄진 컨트롤러와 같이 가능한 경우에는 WebFlux는 RxJava 등 다른 reactive 라이브러리 사용에도 잘 적용된다. 자세한 내용은 Reactive Libraries를 참조하라.

Reactive API 외에도 WebFlux는 명령형 프로그래밍을 제공하는 Kotlin의 Coroutines API와 함께 사용할 수도 있다.

프로그래밍 모델

spring-web 모듈은 reactive 기반으로써 Spring WebFlux를 포함한다. (이는 HTTP 추상체, 서버를 지원하는 Reactive Streams 어댑터, codecs, Servlet API와 유사하나 non-blocking contract를 제공하는 core WebHandler API를 포함한다.)

이를 기반으로 Spring WebFlux는 두 종류의 프로그래밍 모델을 선택할 수 있다.

  • Annotated Controllers : Spring MVC와 일치하며 spring-web 모듈과 동일한 어노테이션을 기반으로한다. Spring MVC 및 WebFlux 컨트롤러는 모두 반응형(Reactor and RxJava) 반환 타입을 지원하기때문에 크게 다르지 않다. 차이점이라고 하면 WebFlux가 반응형 아규먼트인 @RequestBody를 지원한다는 것이다.

  • Functional Endpoints : Lambda 기반의 경량 함수형 프로그래밍 모델이다. 어플리케이션이 요청을 라우팅하고 처리하는데 사용할 수 있는 작은 라이브러리 혹은 유틸리티 집합으로 생각할 수 있다. annotated Controller와의 큰 차이점은 어플리케이션이 처음부터 끝까지 요청 처리를 담당한다는 것과 어노테이션을 통해 의도를 선언하고 콜백을 수행한단 것이다.

적용 가능성

🤔 Spring MVC or WebFlux ?

이분법적으로 이를 나누는 것은 좋지않다. 실제로 두 가지 모두 함께 작동함으로써 사용 가능한 옵션의 범위를 확장시킬 수 있다. 이 둘은 서로의 연속성과 일관성을 위해 설계되었으며 함께 사용가능하고 각자의 피드백이 양쪽 모두에 도움이 된다. 다음 다이어그램은 둘의 관계, 공통점, 고유한 기능을 보여준다.

다음과 같은 것들을 고려하도록 하라.

  • 잘 작동하는 Spring MVC 어플리케이션이 있다면 굳이 변경할 필요가 없다. 명령형 프로그래밍은 코드를 작성하고 이해하고 디버그하기에 가장 쉬운 방법이다. 역사적으로 대부분이 blocking 구조를 가지므로 라이브러리를 최대한으로 선택할 수 있다.
  • 이미 non-blocking 웹 스택을 구매하고 있다면 Spring WebFlux는 이 공간 내에서 다른 것들과 동일한 실행 모델 이점을 제공한다. 또한 서버(Netty, Tomcat, Jetty, Undertow, and Servlet 3.1+ containers), 프로그래밍 모델(annotated controllers, 함수형 웹 앤드포인트), reactive 라이브러리(Reactor, RxJava 등)을 선택할 수 있다.
  • Java 8의 lambda나 Kotlin과 함께 사용하기 위한 가벼운 함수형 웹 프레임워크에 관심이 있는 경우 Spring WebFlux 함수형 웹 앤드포인트를 사용할 수 있다. 더 큰 투명성과 제어의 이점을 누릴 수 있도록 복잡하지 않은 소규모 어플리케이션 또는 마이크로 서비스에 적합한 선택일 수 있다.
  • 마이크로 서비스 아키텍처에서는 Spring MVC, Spring WebFlux 컨트롤러, Spring WebFlux 함수형 앤드포인트와 어플리케이션을 혼합할 수 있다. 두 프레임 워크에서 동일한 어노테이션 기반 프로그래밍 모델을 지원함으로써 올바른 작업과 적합한 도구를 선택하는 동시에 지식을 쉽게 재사용할 수 있다.
  • 응용 프로그램을 평가하는 간단한 방법은 응용 프로그램의 종속성을 확인하는 것이다. 사용할 blocking persistence API(JPA, JDBC) 또는 네트워킹 API가 있는 경우 적어도 공통 아키텍처에는 Spring MVC가 최선의 선택이다. Reactor와 RxJava 둘 다 별도의 스레드에서 blocking 호출을 수행하는 것이 기술적으로 가능하지만 이는 non-blocking 웹 스택을 최대한 활용하지는 못하는 것이다.
  • 원격 서비스에 대한 호출이 있는 Spring MVC 응용 프로그램이 있는 경우 반응형 WebClient를 사용하라. Spring MVC 컨트롤러 메소드는 반응형 타입(Reactor, RxJava 등)을 직접 반환할 수 있다. 호출 당 지연 시간이나 호출 간 상호의존성이 클수록 이득은 극적으로 나타난다. 스프링 MVC 컨트롤러는 다른 reactive component도 호출할 수 있다.
  • 대규모 팀이 있는 경우 non-blocking, 함수형, 선언적 프로그래밍으로의 전환에서 큰 러닝 커브가 있음을 기억하라. 일괄 전환없이 이를 시작하는 실용적인 방법으로는 reactive WebClient를 사용하는 것이다. 그 외에도 작은 규모로 변화를 시작하면서 이익을 측정해야한다. (광범위한 적용에 있어서는 그 변화가 불필요 할 것이라고 예상하기 때문이다.) 어떤 이점을 찾아야할지 확실하지 않은 경우, non-blocking I/O 작동 방식(e.g. 단일 스레드 Node.js의 동시성)과 그 효과에 대해 알아보면 도움이 될 것이다.

서버

Spring WebFlux는 Tomcat, Jetty, Servlet 3.1+ 컨테이너 뿐만 아니라 Netty와 Undertow와 같은 non-servlet 런타임에서도 지원된다. 모든 서버는 로우 레벨의 공통 API에 맞게 조정됨으로 서버 전체에서 하이 레벨의 프로그래밍 모델을 지원 할 수 있다.

Spring WebFlux는 내부적으로 서버를 시작하거나 종료하는 것은 지원하지 않는다. 그러나 Spring configuration과 WebFlux infrastructure로부터 어플리케이션을 조립하는 것은 간단하며 몇 줄의 코드로 이를 실행 시킬 수 있다.

Spring Boot에는 이러한 단계를 자동화하는 WebFlux starter가 있다. 기본적으로 starter는 Netty를 사용하지만 Maven/Gradle 종속성을 변경함으로써 Tomcat, Jetty, Undertow로 쉽게 전환 할 수 있다. Spring boot가 Netty를 기본값으로 사용하는 이유는 Netty가 비동기식, non-blocking 공간에서 널리 사용되며 클라이언트와 서버가 리소스를 공유 할 수 있도록 만들기 때문이다.

Tomcat과 Jetty는 Spring MVC와 WebFlux를 함께 사용할 수 있으나 사용되는 방식은 매우 다르다. Spring MVC는 Servlet blocking I/O에 의존하며 어플리케이션이 필요로 할 경우 Servlet API를 직접 사용할 수 있도록한다. 반면에 Spring WebFlux는 Servlet 3.1 non-blocking I/O에 의존하고 로우 레벨 어댑터 뒤에서 Servlet API를 사용한다. 이는 직접적인 사용을 위해 노출되지 않는다.

Undertow의 경우, Spring WebFlux는 Servlet API 없이 직업 Undertow API를 사용한다.

성능

일반적으로 Reactive, non-blocking이 어플리케이션이 더 빨리 작동하도록 만들지는 않는다. WebClient를 사용하여 원격 호출을 병렬로 실행하는 것과 같은 일부 경우에는 가능하나, 전반적으로 non-blocking 방식으로 작업을 수행하는데 더 많은 작업이 필요하므로 처리 시간이 약간 늘어날 수 있다.

reactive, non-blocking의 주된 이점은 적은 수의 고정된 스레드와 적은 메모리로도 확장이 가능하다는 것이다. 이는 어플리케이션이 보다 예측 가능한 방식으로 확장되기 때문에 어플리케이션을 더욱 탄력적(resilient)으로 만들어준다. 그러나 이러한 이점을 보기위해선 약간의 지연 시간(느리고 예측 불가능한 네트워크 I/O를 포함)이 필요하다. reactive stack이 강점을 보이기 시작하면 그 차이는 극적으로 벌어질 수 있다.

동시성 모델

Spring MVC와 Spring WebFlux는 모두 어노테이션으로 구성된 컨트롤러를 지원하지만 동시성 모델과 blocking와 thread에 대한 기본 가정에는 몇 가지 주된 차이점이 있다.

Spring MVC (및 일반적으로 서블릿 어플리케이션)에서는 어플리케이션이 현재 스레드를 차단할 수 있다고 가정한다. (e.g. remote call) 따라서 서블릿 컨테이너는 요청 처리 도중 잠재적인 blocking을 흡수할 수 있도록 큰 스레드 풀을 사용한다.

Spring WebFlux (및 일반적으로 non-blocking 서버)에서는 어플리케이션이 차단되지 않는다고 가정한다. 따라서 non-blocking 서버는 작은 크기의 고정된 스레드 풀(이벤트 루프 워커)을 사용해 요청을 처리한다.

"규모 조정"과 "소수 스레드"는 모순되게 들릴 수 있으나 현재 스레드를 blocking하지 않고 콜백에 의존한단 것은 흡수할 blocking call이 없으므로 추가 스레드가 필요없음을 의미한다.

Blocking API 호출

blocking 라이브러리를 사용해야하는 경우, Reactor와 RxJava는 다른 스레드에서 처리를 지속할 수 있는 publishOn 연산자를 제공한다. 즉, 쉽게 탈출할 수 있는 hatch(비상구)를 보유하고 있단 의미이다. 반면에 blocking API는 이런 동시성 모델에는 적합하지 않다.

가변적 상태

Reactor와 RxJava에서는 연산자를 통해 로직을 선언한다. 런타임에서 별개의 단계에서 순차적으로 데이터를 처리하는 reactive pipeline이 형성된다. 이로인해 해당 파이프라인 내의 어플리케이션 코드는 동시에 호출되지 않으므로 어플리케이션은 변경 가능한 상태를 보호할 필요가 없다.

Threading Model

Spring WebFlux로 실행되는 서버에서 볼 수 있는 스레드는 어떤 것일까?
~생략~

Reactive Core

spring-web 모듈은 reactive web 어플리케이션에 대해 다음과 같은 기본 지원이 포함된다.

  • 서버 요청 처리
    • HttpHandler : Reactor Netty, Undertow, Tomcat, Jetty 및 모든 Servlet 3.1+ 컨테이너용 어댑터와 함께 non-blocking I/O와 Reactive Streams back pressure를 사용하는 HTTP 요청 처리를 위한 기본 Contract
    • WebHandler API : 요청 처리를 위한 약간 더 높은 수준의 범용 웹 API. 어노테이션 구성 컨트롤러 및 함수형 엔드포인트와 같은 구체적인 프로그래밍 모델이 구축된다.
  • 클라이언트 측의 경우, Reactor Netty와 reactive Jetty HTTPClient를 위한 어댑터와 함께 non-blocking I/O와 Reactive Streams back pressure로 HTTP 요청을 수행하는 기본 ClientHttpConnector Contract가 존재한다. 어플리케이션에서 사용되는 고수준의 WebClient는 이런 기본 Contract 위에 쌓아진다.
  • 클라이언트와 서버 모두를 위해 HTTP 요청 및 응답 컨텐츠의 직렬화/역직렬화를 제공하는 codec이 있다.

HttpHandler

HttpHandler는 요청과 응답을 처리하는 단일 메서드가 있는 간단한 contract이다. 유일한 목적은 다른 HTTP server API에 대한 최소화된 추상화이다.

지원되는 서버

서버 종속성

WebHandler API

org.springframework.web.server 패키지는 HttpHandler Contract를 기반으로 구축되어 multiple WebExceptionHandler, multiple WebFilter, 단일 WebHandler 컴포넌트의 체인을 통해 요청을 처리하기위한 범용 웹 API를 제공한다. 간편하게 컴포넌트가 자동으로 감지되는 Spring ApplicationContext를 사용하거나 빌더에 컴포넌트를 등록하여 체인을 WebHttpHandlerBuilder와 함께 사용할 수 있다.

HttpHandler는 다른 HTTP 서버의 사용을 추상화한다는 단순한 목표를 가지고 있지만 WebHandler API는 다음과 같은 웹 애플리케이션에서 일반적으로 사용되는 광범위한 기능들을 제공하는 것을 목표로 한다.

  • attribute를 포함하는 사용자 세션
  • request attribute
  • 요청에 대한 Locale 혹은 Principal에 대한 해결
  • 폼 데이터에 대한 구문 분석과 캐시를 위한 엑세스
  • multipart 데이터에 대한 추상화
profile
🌱 😈💻 🌱

0개의 댓글