본문 바로가기
Spring

Spring Webflux - Overview

by 즐겁게살자 2021. 7. 4.
728x90

Spring weblfus 공식 레퍼런스 번역&요약

 

1.1 Overview

   - spring webflux 가 생겨난 이유

   

1) 적은 쓰레드로 동시 처리를 제어하기 위함.

  • 논 블럭킹 웹 스택
  • 이전에도 서블릿 3.1은 논블럭킹 I/O를 위한 API를 제공했지만, 다른 동기 처리(Filter, Servlet)시에나 블로킹 방식(getParamter, getPart)를 쓰는 API 를 사용하기 어려웠음.
  • 이런 점 때문에 어떤 논 블로킹과도 잘 동작하는 새 공통 API를 만들게 됨.

2) 함수형 프로그래밍

  • continuation-style API(CompletableFuture와 ReactiveX로 대중화된)로 비동기 로직을 선언적으로 작성할 수 있도록 함.

1.1.1 "Reactive" 정의

    

  •   "리액티브" 라는 용어는 변화에 반응하는 것을 중심에 두고 만든 프로그래밍 모델을 의미한다. (I/O 이벤트에 반응하는 네트워크 컴포넌트, 마우스 이벤트에 반응하는 UI 컨트롤러 등).
  • 스프링은 "리액티브:와 관련한 중요한 메커니즘이 하나 더 있는데, 논블로킹 back pressure다. 동기식 명령형(imperative: 명령적인 ) 코드에서 블로킹 호출은 호출자를 강제로 기다리게 하는 일종의 backpressure다.
  • 리액티브 스트림을 쓰는 주 목적은 subscriber가 publisher의 데이터 생산 속도를 제어하는 것이다. publicsher 의 속도를 늦츨 수 없다면 어떻게 할까? subscriber가 소화하지 못할 경우를 대비하여, 버퍼에 담을지, 데이터를 날릴지, 실패로 처리할 지 결정해야 한다.

1.1.2 Reactive API

 

  • Reactor는 Java8 스트림 API 처럼 비동기 로직을 위한 고수준 함수형 API 이다. 
  • Reactor는 스프링 웹플럭스가 선택한 리액티브 라이브러리이다. 
  • Reactor는 MonoFlux API 타입을 제공한다.
  • ReactiveX vocabulary of perators 에 정리 된 연산자들을 사용해 데이터 시퀀스를 0~1개는 Mono, 0~N개는 Flux로 표현할 수 있다.
  • 리액티브 API와는 별개로 웹플럭스는 코틀린의 코루틴 API와도 사용할 수 있는데, 이를 사용하면 좀 더 명령적인(Imperative) 프로그래밍이 가능하다. 

1.1.3 Programming Models

 

spring-web 모듈에 있는 웹플럭스는, 여러 서버를 지원하기 위한 HTTP 추상화와 리액티브 스트림 어댑터, 코덱, Servlet API에 상응하는 코어 WebHandler API를 아우르는 개념이며, 이는 모두 논 블로킹이다.

 

스프링 웹플럭스는 두 가지 프로그래밍 모델을 지원한다.

 

  • Annotated Controllers: 스프링 MVC와 동일하며 spinrg-web 모듈에 있는 같은 어노테이션을 사용한다.
    스프링 MVC와 웹플럭스 컨트롤러 모두 리액티브(Reactor, RxJava)리턴 타입을 지원하기 때문에 이 둘을 구분하기 어렵다. 
    한 가지 눈에 띄는 차이는 웹플럭스에선 @ResponseBody로 리액티브 인자를 받을 수 있다는 것이다.

  • Functional Endpoints: 경량화된 람다 기반 함수형 프로그래밍 모델. 요청을 라우팅 해주는 조그만한 라이브러리나 유틸리티 모음이라고 생각하면 된다. annoated controller와 다른 점은 어노테이션으로 의도를 선언해서 콜백 받기보단 요청을 어플리케이션이 처음부터 끝까지 제어한다는 것이다.

1.1.4 Applicability

스프링 MVC냐 웹플럭스냐? 

 

많이들 하는 질문이지만 이분법적 사고는 좋지 않다. 둘 모두 선택의 폭을 넓혀 준다고 보는 게 맞다. 이둘은 지속성과 일관성을 위해 설계했으며, 함께 사용할 수 있고, 각자의 피드백에 서로에게 도움이 된다. 다음은 둘이 어떤 관련이 있는지, 공통점이 무엇이고 한쪽에서만 지원하는 게 무엇인지 나타낸 다이어그램이다.

 

다음 제안을 고려해 보라.

  • 이미 잘 동작하고 있는 스프링 MVC 어플리케이션이 있다면, 굳이 바꿀 필요 없다. (블로킹 방식으로 처리되고 있는 로직들을 부조건 바꿀 필욘 없다는 뜻)
  • 논 블록킹 웹 스택을 알아보고 있다면, 스프링 웹플럭스는 다른 웹 스택과 같은 실행 환경을 제공하면서도, 다양한 서버(Netty, Tomcat, Jetty, Undertow, 서블릭 3.1+ 컨테이너)와 여러 리액티브 라이브러리(리액터, JxJava 등)를 지원하며, 두 가지 프로그래밍 모델(어노테이션을 선언한 컨트롤러와 함수형 웹 엔드포인트)을 사용 할 수 있다
  • java8 람다나 코틀린으로 개발할 수 있는 경량의 함수형 웹 프레임워크를 찾고 있다면, 스프링 웹플럭스 함수형 웹 엔드포인트를 사용하면 된다. 로직을 투명하게 제어할 수 있기 때문에 요구사항이 덜 복잡한 소규모 어플리케이션이나 마이크로 서비스에서도 좋은 선택이 될 것이다.
  • MSA 에선 스프링 mvc로 만든 어플리케이션과, 스프링 웹플럭스 컨트롤러나 함수형 엔트 포인트를 사용한 어플리케이션을 조합할 수 있다. 도 프레임워크 모두 어노테이션 기반 프로그래밍 모델을 지원하기 때문에 새로 학습할 필요 없이 각자에 맞는 툴을 선택할 수 있다. 
  • 스프링 MVC 어플리케이션에서 외부 서비스를 호출한다면 한번 리액티브 WebClient를 사용해봐라. 스프링 MVC 컨트롤러 메소드에서도 리액티브 타입(Reactor, RxJava나 그 외)를 반환할 수 있다. 서비스 호출에 지연이 있거나 여러 서비스가 엮여 있는 API라면 효과가 더 좋을 것이다. 물론 다른 리액티브 컴포넌트도 스프링 MVC 컨트롤러에서 호출할 수 있다.
  • 팀 규모가 크다면 논블로킹, 함수형, 선언적 프로그래밍은 러닝커브가 높다는 점도 고려해야 한다. 한 번에 전환하지 않고 리액티브 WebClient 부터 적용해 보는 것도 좋은 방법이다. 작은 것부터 시작해서 변화가 있는지 확인해 봐라. 굳이 전환할 필요가 없는 경우도 많을 것이다. 어떤 변화를 확인해야 할지 감이 오지 않는다면, 논블로킹 I/O 동작 방식과 효과를 학습하는 것부터 시작해라(예를 들어 싱글 쓰레드 기반 Node.js의 동시처리).

1.1.5. Servers

 

  • 스프링 웹플럭스는 톰캣, Jetty, 서블릿 3.1 + 컨테이너에도, 서블릿 기반이 아닌 Netty나 Undertow에서도 잘 동작한다. 저 수준 공통 API로 서버로 서버를 추상화 하기 때문에 모든 서버에 고수준 프로그래밍 모델을 적용 할 수 있다.

  • 스프링 부트에선 웹플럭스 스타터가 이 단계를 자동화해준다. 스타터는 기본으로 Netty를 사용하지만, 메이븐이나 그래들 dependency만 수정하면 톰캣이나 jetty, Undertow로 쉽게 교체할 수 있다. 스프링 부트가 Netty를 디폴트로 사용하는 이유는 보통 비동기 논블로킹에 많이 사용하기도 하고, 클라이언트와 서버가 리소스를 공유할 수 있어서다.

  • 톰캣과 jetty는 스프링 MVC, 웹플럭스 모두 사용할 수 있다. 하지만 동작 방식이 다르다는 점에 주의하라. 스프링 MVC는 서블릿의 블로킹 I/O를 사용하며, 어플리케이션에서 필요하면 서블릿 API를 직접 사용할 수 있다. 스프링 웹플럭스는 서블릿 3.1 논블로킹 I/O로 동작하며, 서블릿 API는 저수준 어댑터에서 사용하기 때문에 노출돼 있지 않다. 

  • 스프링  웹플럭스에서 Undertow를 사용할 때는 서블릿 API가 아닌 Undertow API를 사용한다.


1.16. Performance

 

성능은 여러 의미로 해석할 수 있다. 리액티브랑 논블로킹을 사용한다고 해서 바로 어플리케이션이 빨라지는 건 아니다. 물론 빨라질 수도 있다. (예를 들어 WebClient를 사용해서 외부 서비스 호출을 병렬로 처리한다면). 전반적으로 보면 논블로킹 방식이 처리 할 일이 더 많다 보니 처리 시간이 약간 더 길어질 수 있다.

 

리액티브와 논블로킹의 주된 이점은 고정된 적은 쓰레드과 적은 메모리로도 확장할 수 있다는 것이다. 예측할 수 있는 방법으로 확장하기 때문에 부하 속에서도 어플리케이션 복원 능력은 더 좋아진다. 하지만 이를 확인하려면 약간의 대기 시간이 필요하다 (느리고 예측 불가능한 네트워크 I/O 시간을 포함해서). 바로 여기서 리액티브 스택이 강점을 드러내며, 그 차이는 엄청나다.

 


1.17. Cuncrrency Model

 

스프링 MVC와 스프링 웹플럭스 둘 다 annotated controller를 사용할 수 있다는 점은 동일해도, 동시성 모델과 블로킹/쓰레드 기본 전략이 다르다.

 

스프링 MVC는(일반적인 서블릿 어플리케이션) 어플리케이션이 처리 중인 쓰레드가 잠시 중단 될 수 있다.(예를 들어 외부 서비스를 호출하면). 그렇기 때문에 서블릿 컨테이너는 이 블로킹을 대비에 큰 쓰레드 풀로 요청을 처리한다.

 

스프링 웹플럭스는 (그리고 일반적인 논블로킹 서버라면) 실행 중인 쓰레드가 중단되지 않는다는 전제가 있다. 따라서 논블로킹 서버는 작은 쓰레드 풀(이벤트 루프 워크)을 고정해놓고 요청을 처리한다.

 

Invoking a Blocking API

  블로킹 라이브러리를 사용해야 한다면 어떻게 해야 할까? 리액터, RxJava 모두 다른 쓰레드로 요청을 처리해 주는 publishOn 오퍼레이터를 지원한다. 블로킹을 쉽게 피해갈 수 있다는 말이긴 하지만, 블로킹 API 자체가 동시성 모델에 적한하지 않다는 걸 유념하라.

 

Mutable State

  리액터와 Rxjava에서 로직은 연산자로 표현한다. 연산자를 사용하면 런타임에 분리된 환경에서 리액티브 파이프라인을 만들고, 각 파이프라인에서 데이터를 순차적으로 처리한다. 파이프라인 안에 있는 코드는 절대 동시에 실행 되지 않으므로 더이상 상태 공유(mutable state)를 신경쓰지 않아도 된다.

 

Treading Model

 

스프링 웹플럭스를 사용하는 어플리케이션은 어떤 쓰레드를 얼마나 실행할까?

  • 최소한의 설정으로 스프링 웹플럭스 서버를 띄우면(예를 들면 데이터 접근 같은 다른 dependency가 없는), 서버는 쓰레드 한 개로 운영하고, 소량의 쓰레드로 요청을 처리 할 수 있다. (보통은 CPU코어 수 만큼). 하지만 서블릿 컨테이너는 서블릿 블로킹 I/O와 서블릿 3.1 논블로킹 I/O를 모두 지원하기 때문에 더 많은 쓰레드를 실행 할 것이다. (예를 들어 톰캣은 10개)
  • 리액티브 WebClient는 이벤트 루프를 사용한다. 따라서 적은 쓰레드를 고정해 두고 쓴다(예를 들어 리액터 Netty 컨넥터를 쓴다면 reactor-http-nio- 로 시작하는 쓰레드를 확인할 수 있다). 단, 클라이언트와 서버에서 모두 Netty를 사용하면 디폴트로 루프 리소스를 공유한다.
  • 리액터와 RxJava는 스케줄러라는 추상화된 쓰레드 풀 전략을 제공한다. publishOn 연산자가 나머지 연산을 다른 쓰레드 풀로 전환할 때도 이 스케줄러를 사용한다. 스케줄러는 이름을 보면 동시 처리 전략을 알 수 있다. 예를 들어, 제한된 쓰레드로 CPU 연산이 많은 처리를 할 때는 "parallel", 여러 쓰레드로 I/O 가 많은 처리를 할 때는 "elastic"이다. 이런 쓰레드를 본다면 코드 어딘가에서 그 이름에 해당하는 쓰레드 풀 scheduler 전략을 사용하고 있다는 뜻이다. 

Configuring

 

스프링 프레임워크에서 서버를 직접 실행시키거나 중단할 수는 없다. 서버의 쓰레드 모델을 바꾸고 싶다면 각 서버에 많은 설정 API를 참고하거나, 아니면 스프링 부트는 써서 각 서버에 맞는 스프링 부트 옵션을 설정하면 된다. WebClient 는 코드로 직접 설정 할수 있다. 다른 라이브러리는 해당 라이브러리 문서를 확인하라.

'Spring' 카테고리의 다른 글

WebFlux - Functional Endpoints  (0) 2021.09.12
Spring Webflux - DispatcherHandler  (0) 2021.07.12
Spring Webflux - Reactive Core  (0) 2021.07.05

댓글