[스프링 웹플럭스] 1.2.1. HttpHandler

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-httphandler

모든 HTTP 서버는 HTTP 요청을 처리하는데 필요한 API를 제공한다. HttpHandler는 요청과 응답을 처리하는데 사용하는 메서드 하나를 정의한 간단한 컨트랙이다. 의도저으로 최소화했다. 주요 목적은 각기 다른 서버에서 리액티브 스프림 기반 API에서 HTTP 요청을 처리할 때 사용할 공통 인터페이스를 제공하는 것이다.

spring-web 모듈은 여러 서버를 지원하는 어댑터를 제공한다. 아래 테이블은 지원하는 서버와 어떤 리액티브 스트림을 사용하는지 보여준다.

Server name Server API used Reactive Streams support

Netty

Netty API

Reactor Netty

Undertow

Undertow API

spring-web: Undertow to Reactive Streams bridge

Tomcat

Servlet 3.1 non-blocking I/O; Tomcat API to read and write ByteBuffers vs byte[]

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

Jetty

Servlet 3.1 non-blocking I/O; Jetty API to write ByteBuffers vs byte[]

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

Servlet 3.1 container

Servlet 3.1 non-blocking I/O

spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

다음은 필요로 하는 의존성과, 지원하는 버전과 각 서버 코드 예제다.

Server name Group id Artifact name

Reactor Netty

io.projectreactor.ipc

reactor-netty

Undertow

io.undertow

undertow-core

Tomcat

org.apache.tomcat.embed

tomcat-embed-core

Jetty

org.eclipse.jetty

jetty-server, jetty-servlet

Reactor Netty:

HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

Undertow:

HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();

Tomcat:

HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);

Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();

Jetty:

HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);

Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();

핸들러를 ServletHttpHandlerAdapter를 사용해서 Servelt으로 감싸서 서블릿 3.1 컨테이너에 WAR로 배포할 수도 있다.

[스프링 웹플럭스] 1.2. 리액티브 스프링 웹

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-reactive-spring-web

spring-web 모듈은 저 수준 기반 시설과 HTTP 추상화를 제공한다. 리액티브 웹 애플리케이션을 개발하는데 필요한 클라이언트와 서버를 제공한다. 모든 공개 API는 리액티브 스트림으로 만들었고 리액터를 그것을 받쳐주는 구현체로 사용했다.

서버 기능은 두 계층으로 구성되어 있다.

  • HttpHandler와 서버 어댑터 –  HTTP 요청을 리액티브 스트림 백 프레셔로 처리하는 가장 기본적이고 공통적인 API
  • WebHandler API – 그보다 약간 높은 계층이지만 필터 체인 스타일의 프로세싱을 담고 있는 일반적인 목적의 서버 웹 API

[스프링 웹플럭스] 1.1.7. 성능 VS 확장성

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-performance

성능이란 다양한 특징과 의미가 있다. 보통, 리액티브와 논-블럭킹은 애플리케이션을 더 빠르게 만들어 주진 않는다. 몇몇 경우엔 그럴 수도 있다. 예를 들어, WebClient를 사용하여 병렬적으로 여러 리모트 콜을 실행할 수 있다. 하지만 전체적으로 볼 때 논-블럭킹 방식을 위해 더 많은 작업을 해야하기 때문에 필요한 처리 시간이 약간 증가할 수 있다.

리액티브와 논-블럭킹으로 기대할 수 있는 주요 장점은 작고, 고정된 개수의 쓰레드와 더 적은 메모리를 사용하여 확장할 수 있다는 점이다. 예측 가능한 방법으로 확장할 수 있기 때문에 부하를 받는 상황에서 애플리케이션을 더 견고하게 만들어 준다. 하지만 그러한 장점을 관찰하려면 느리고 예측 불가능한 네트워크 IO를 혼합한 어느정도의 지연이 필요하다. 바로 그 부분이 리액티브 스택이 장점을 보여주는 지점이며 그 차이는 극적일 것이다.

[스프링 웹플럭스] 1.1.6. 서버 선택하기

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-server-choice

스프링 웹플럭스는 네티, 언더토우, 톰캣, 제티와 서블릿 3.1+ 컨테이너를 지원한다. 모두 리액티브 스트림 API를 채용했다. 스프링 웹플럭스 프로그래밍 모델은 그러한 공통 API에 기반하여 만들었다.

자주 받는 질문: 톰캣이랑 제티를 어떻게 두 가지 스택에서 다 쓸 수 있는거지?

톰캣과 제티의 코어 자체는 논-블러킹이다. 하지만 서블릿 API가 그 위에 블럭킹 퍼사드를 추가했다. 서블릿 3.1 API부터 논-블럭킹 I/O를 제공하는 선택지를 제공하지만 그것을 사용하려면 다른 동기적이고 블럭킹 요소를 피하도록 주의해야 한다. 그러한 이유로 스프링의 리액티브 웹 스택은 리액티브 스트림과 연결해주는 로우 레벨 서블릿 어댑터을 가지고 있으며 서블릿 API를 노출 시키지 않아서 직접 사용할 수 없도록 했다.

스프링 부트 2는 웹플럭스를 사용할 때 네티를 기본으로 사용한다. 왜냐면 네티가 비공기, 논-블럭킹 영역에서 가장 널리 사용되고 있으며 클라이언트과 서버 모두 공유할 수 있는 리소스를 제공한다. 서블릿 3.1 논-블럭킹은 아직 많이 사용되지 않았는데 쓰기 어렵기 떄문이다. Spring WebFlux opens one practical path to adoption. (?)

스프링 부트가 기본 서버를 선택할 때 주로 바로 사용할 수 있는 것(out-of-the-box)을 선택한다. 애플리케이션은 여전히 성능 최적화 되어있고, 완전히 논-블럭킹이고 리액티브 백 프레셔를 채택한 어떤 서버든 선택할 수 있다. 스프링 부트에서 매우 손쉽게 서버를 바꿀 수 있을 것이다.

[스프링 웹플럭스] 1.1.5. 웹 프레임워크 선택하기

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-programming-models

스프링 MVC를 써야 하나 웹플럭스를 써야 하나? 차이점을 살펴보자.

이미 동작하는 스프링 MVC 애프리케이션이 있다면 그대로도 괜찮다. 바꿀 필요는 없다. 절차적인 프로그래밍은 가장 쉽게 작성하고, 이해하며 디버깅할 수 있는 코드다. 역사적으로 대부분이 블럭킹인 수많은 라이브러리를 선택해 사용할 수 있다.

논-블럭킹 웹 스택을 이미 살펴보고 있다면, 스프링 웹플럭스는 그 영역에서 다른 것들과 동일한 실행 모델을 제공하며 서버를 선택해 사용할 수 있게끔 해준다. 네티, 톰캣, 제티, 언더토우, 서블릿 3.1+ 컨테이너를 사용할 수 있다. 프로그래밍 모델로는 애노테이션 컨트롤러와 펑셔널 웹 엔드포인트 그리고 리액터, RxJava 등의 리액티브 라이브러리를 선택해 사용할 수 있다.

가벼운 펑셔널 웹 프레임워크를 자바 8 람다나 코틀린으로 사용하고 싶다면 스프링 웹 플럭스 펑셔널 웹 엔드포인트를 사용할 수 있다. 작은 애플리케이션이나 복잡도가 낮은 마이크로서비스용으로 사용하여 투명성과 제어에 대한 장점을 살릴 수 있다.

마이크로서비스 아키텍처 안에서 스프링 MVC 애플리케이션과 스프링 웹플럭스 컨트롤러 또는 스프링 웹플럭스 펑셔널 엔드포인트를 혼용할 수 있다. 똑같은 애노테이션 기반의 프로그래밍 모델을 양쪽 프레임워크 모두에 사용함으로써 기존 지식의 재사용을 편하게 하며 적절한 곳에 적절한 툴을 선택해 사용하게 해준다.

애플리케이션을 평가하는 쉬운 방법중 하나는 의존성을 확인하는 것이다. 만약에 블록킹 영속화 API (JPA, JDBC)를 사용하거나 네트워크 API를 사용한다면 스프링 MVC가 가장 흔한 아키텍처로는 최적이다. 기술적으로는 리액터나 RxJava로도 블럭킹 호출을 별도의 쓰레드로 처리하는게 가능하지만 대부분의 논-블럭킹 웹 스택에서는 그런 쓰레드를 만들지 않는다.

리모트 서비스를 호출하는 스프링 MVC 애플리케이션이 있다면 리액티브 WebClient를 사용해보자. 스프링 MVC 애플리케이션 메서드에서 리액티브 타입 (리액터, RxJava, 그리고 다른 것)을 직접 반환할 수 있다. 호출 당 지연이 크거나 상호 의존도가 클 수록 효과를 크게 볼 수 있다. 스프링 MVC 컨트롤러는 다른 리액티브 컴포넌트를 호출할 수 도 있다.

대규모 팀이라면 논-블럭킹과 선언적인 프로그래밍으로 옮기는데 드는 학습 비용을 염두해야 한다. 실용적인 방법은 전체를 전화하는게 아니라 리액티브 WebClient를 사용하는것 부터 시작하는 것이다. 조금씩 바꾸기 시작한 다음 그 효과를 측정하라. 애플리케이션 전체를 다 바꿀 필욘 없을 것으로 예상한다.

어떤 장점을 얻을 수 있는지 확실하지 않다면, 논-블럭킹 I/O의 동작 방법(단일 쓰레드 Node.js의 동시성은 모순이 아니다.)과 그 효과에 대해 학습하라. 주목할 점은 “하드웨어는 조금 쓰면서 확장하는 것”이지만 느리거나 예측 불가능한 어느정도의 네트워크 I/O 없이는 그 효과를 보장하진 않는다. 넷플릭스의 이 블로그가 참고하기 좋다.