Loading...
Spring Framework Reference Documentation 7.0.2의 Overview의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Why was Spring WebFlux created?
답변의 일부는 적은 수의 thread로 concurrency를 처리하고 더 적은 hardware resource로 scale 하기 위한 non-blocking web stack의 필요성입니다. Servlet non-blocking I/O는 contract가 synchronous(Filter, Servlet)이거나 blocking(getParameter, getPart)인 나머지 Servlet API에서 벗어나는 방향으로 이어집니다. 이것이 어떤 non-blocking runtime에서도 기반이 될 수 있는 새로운 공통 API를 위한 동기가 되었습니다. 이는 async, non-blocking 영역에서 잘 정립된 server(Netty 등)들 때문에 중요합니다.
답변의 다른 일부는 functional programming입니다. Java 5에서 annotation이 추가되었을 때(annotated REST controller나 unit test 같은) 기회가 생겼던 것처럼, Java 8에서 lambda expression이 추가되면서 Java에서 functional API를 위한 기회가 생겼습니다. 이는 non-blocking application과 asynchronous logic의 선언적 구성을 가능하게 하는 continuation-style API(CompletableFuture와 ReactiveX에 의해 대중화된)에게 큰 도움이 됩니다.
programming-model 수준에서 Java 8은 Spring WebFlux가 annotated controller와 함께 functional web endpoint를 제공할 수 있도록 했습니다.
우리는 “non-blocking”과 “functional”에 대해 언급했지만, reactive는 무엇을 의미할까요?
“reactive”라는 용어는 변화에 반응하도록 구축된 programming model을 가리킵니다 — network component가 I/O event에 반응하고, UI controller가 mouse event에 반응하는 것 등입니다. 그런 의미에서, non-blocking은 reactive입니다. 왜냐하면 block되는 대신, 이제 operation이 완료되거나 data가 사용 가능해짐에 따라 notification에 반응하는 모드에 있기 때문입니다.
또한 우리가 Spring 팀에서 “reactive”와 연관 짓는 또 다른 중요한 메커니즘이 있는데, 그것은 non-blocking back pressure입니다. synchronous, imperative code에서 blocking call은 caller가 기다리도록 강제하는 자연스러운 형태의 back pressure 역할을 합니다. non-blocking code에서는 빠른 producer가 대상 지점을 압도하지 않도록 event의 rate를 제어하는 것이 중요해집니다.
Reactive Streams는 back pressure를 가진 asynchronous component 간의 상호작용을 정의하는 small spec (Java 9에서 adopted되기도 했습니다)입니다. 예를 들어 data repository가 Publisher로서 data를 생성하고, HTTP server가 Subscriber로서 그것을 response에 쓸 수 있습니다. Reactive Streams의 주요 목적은 subscriber가 publisher가 data를 얼마나 빠르거나 느리게 생성할지 제어할 수 있게 하는 것입니다.
Common question: what if a publisher cannot slow down? Reactive Streams의 목적은 단지 메커니즘과 경계를 설정하는 것입니다. publisher가 속도를 늦출 수 없다면, buffer를 사용할지, drop할지, fail할지를 결정해야 합니다.
Reactive Streams는 interoperability에서 중요한 역할을 합니다. 이는 library와 infrastructure component에 관심이 있지만, 너무 low-level이기 때문에 application API로서는 덜 유용합니다. application은 collection에만 국한되지 않는, async logic을 구성하기 위한 Java 8 Stream API와 유사한, 더 high-level이고 풍부한 functional API가 필요합니다.
이것이 reactive library가 맡는 역할입니다.
Reactor는 Spring WebFlux를 위한 reactive library입니다. Reactor는
Mono와
Flux API type을 제공하여
0..1(Mono)과 0..N(Flux)의 data sequence를 ReactiveX vocabulary of operators에 맞춰진 풍부한 operator 집합을 통해 다룰 수 있게 합니다. Reactor는 Reactive Streams library이며, 따라서 그 operator 전부가 non-blocking back pressure를 지원합니다.
Reactor는 server-side Java에 강하게 초점을 맞추고 있습니다. 이는 Spring과 긴밀한 협업을 통해 개발됩니다.
WebFlux는 core dependency로 Reactor를 필요로 하지만 Reactive Streams를 통해 다른 reactive library와 interoperable합니다. 일반적인 규칙으로, WebFlux API는 입력으로 plain Publisher를 받아 내부적으로 Reactor type으로 변환하여 그것을 사용하고, 출력으로 Flux 또는 Mono를 반환합니다.
따라서 어떤 Publisher든 입력으로 전달할 수 있고 출력에 operation을 적용할 수 있지만, 다른 reactive library에서 사용하려면 출력을 변환해야 합니다. 가능한 경우(예를 들어 annotated controller) WebFlux는 RxJava나 다른 reactive library의 사용에 투명하게 적응합니다.
자세한 내용은 Reactive Libraries를 참조하십시오.
Reactive API 외에도, WebFlux는 Kotlin에서 보다 imperative한 programming style을 제공하는 Coroutines API와 함께 사용할 수도 있습니다. 아래의 Kotlin code sample은 Coroutines API와 함께 제공될 것입니다.
spring-web module에는 Spring WebFlux의 기반이 되는 reactive foundation이 포함되어 있으며, 여기에는 HTTP abstraction, 지원되는 server를 위한 Reactive Streams adapter,
codec, 그리고 Servlet API와 비교 가능한 core
WebHandler API가 포함되어 있지만, contract는 non-blocking입니다.
그 기반 위에 Spring WebFlux는 두 가지 programming model 중 하나를 선택할 수 있게 제공합니다:
Annotated Controllers: Spring MVC와 일관성을 가지며 spring-web module의 동일한 annotation을 기반으로 합니다. Spring MVC와 WebFlux controller 모두 reactive(Reactor와 RxJava) return type을 지원하며, 그 결과 둘을 구분하기가 쉽지 않습니다. 눈에 띄는 차이점 하나는 WebFlux가 reactive @RequestBody argument도 지원한다는 점입니다.
Functional Endpoints: Lambda 기반의 lightweight하고 functional한 programming model입니다. 이를 application이 request를 routing하고 처리하는 데 사용할 수 있는 작은 library 또는 utility 집합으로 생각할 수 있습니다. annotated controller와의 큰 차이점은 application이 annotation을 통해 의도를 선언하고 callback을 받는 대신, 처음부터 끝까지 request handling을 책임진다는 점입니다.
Spring MVC 또는 WebFlux?
자연스럽게 떠오르는 질문이지만, 건전하지 못한 이분법을 설정하는 질문이기도 합니다. 실제로 둘은 함께 작동하여 사용 가능한 option의 범위를 확장합니다. 두 framework는 서로 간의 연속성과 일관성을 위해 설계되었으며, 나란히 제공되고, 각 측면에서 나온 feedback은 양측 모두에 이득이 됩니다. 다음 diagram은 둘이 어떻게 연관되는지, 무엇을 공통으로 가지는지, 그리고 각각이 고유하게 지원하는 것이 무엇인지 보여줍니다:

다음의 구체적인 사항을 고려해 보기를 권장합니다:
Spring MVC application이 잘 동작하고 있다면 변경할 필요가 없습니다. Imperative programming은 code를 작성하고 이해하고 debug하기에 가장 쉬운 방법입니다. 역사적으로 대부분이 blocking이었기 때문에 library 선택의 폭이 최대입니다.
이미 non-blocking web stack을 찾고 있다면, Spring WebFlux는 이 영역의 다른 것들과 동일한 execution model상의 이점을 제공할 뿐만 아니라 server(Netty, Tomcat, Jetty, 그리고 Servlet container)의 선택, programming model(annotated controller와 functional web endpoint)의 선택, reactive library(Reactor, RxJava, 또는 기타)의 선택을 제공합니다.
Java 8 lambda나 Kotlin과 함께 사용할 lightweight한 functional web framework에 관심이 있다면 Spring WebFlux functional web endpoint를 사용할 수 있습니다. 이는 또한 더 큰 투명성과 제어의 이점을 누릴 수 있는, 덜 복잡한 요구 사항을 가진 작은 application이나 microservice에 좋은 선택이 될 수 있습니다.
microservice architecture에서는 Spring MVC 또는 Spring WebFlux controller, 혹은 Spring WebFlux functional endpoint를 사용하는 application을 혼합해서 가질 수 있습니다. 두 framework 모두에서 동일한 annotation 기반 programming model을 지원하기 때문에, 올바른 작업에 올바른 도구를 선택하는 동시에 지식을 재사용하기가 더 쉽습니다.
application을 평가하는 간단한 방법은 dependency를 확인하는 것입니다. 사용해야 할 blocking persistence API(JPA, JDBC)나 networking API가 있다면, 적어도 일반적인 architecture에서는 Spring MVC가 최선의 선택입니다. 기술적으로는 Reactor와 RxJava 모두에서 별도의 thread에서 blocking call을 수행하는 것이 가능하지만, 그렇게 하면 non-blocking web stack을 최대한 활용하는 것은 아닙니다.
원격 service 호출이 있는 Spring MVC application이 있다면 reactive WebClient를 사용해 보십시오.
Spring MVC controller method에서 reactive type(Reactor, RxJava, 또는 기타)을 직접 반환할 수 있습니다.
호출당 latency가 크거나 호출 간 상호 의존성이 클수록 이점은 극적입니다.
Spring MVC controller는 다른 reactive component도 호출할 수 있습니다.
팀 규모가 크다면 non-blocking, functional, declarative programming으로 전환하는 데 따른 가파른 학습 곡선을 염두에 두어야 합니다. 전체 전환 없이 시작할 수 있는 실용적인 방법은 reactive WebClient를 사용하는 것입니다. 그 이후에는 작게 시작하고 이점을 측정하십시오.
우리는 광범위한 application에 대해 이러한 전환이 불필요하다고 예상합니다. 어떤 이점을 찾아야 할지 확신이 없다면, non-blocking I/O가 어떻게 작동하는지(예: single-threaded Node.js에서의 concurrency)와 그 영향에 대해 학습하는 것부터 시작하십시오.
Spring WebFlux는 Tomcat, Jetty, Servlet container뿐만 아니라 Netty 같은 non-Servlet runtime에서도 지원됩니다. 모든 server는 low-level common API에 적응되어, 상위 수준의 programming model이 server 전반에서 지원될 수 있습니다.
Spring WebFlux에는 server를 시작하거나 중지하는 built-in support가 없습니다. 그러나 Spring configuration과 WebFlux infrastructure에서 application을 assemble하고 몇 줄의 code로 run it하는 것은 쉽습니다.
Spring Boot에는 이러한 단계를 자동화하는 WebFlux starter가 있습니다. 기본적으로 starter는 Netty를 사용하지만, Maven 또는 Gradle dependency를 변경하여 Tomcat이나 Jetty로 쉽게 전환할 수 있습니다. Spring Boot는 async, non-blocking 영역에서 더 널리 사용되고 client와 server가 resource를 공유할 수 있게 해주기 때문에 기본값으로 Netty를 사용합니다.
Tomcat과 Jetty는 Spring MVC와 WebFlux 모두에서 사용할 수 있습니다. 그러나 이들이 사용되는 방식은 매우 다르다는 점을 명심하십시오. Spring MVC는 Servlet blocking I/O에 의존하며 필요하다면 application이 Servlet API를 직접 사용하도록 허용합니다. Spring WebFlux는 Servlet non-blocking I/O에 의존하며 low-level adapter 뒤에서 Servlet API를 사용합니다. 이는 직접 사용을 위해 노출되지 않습니다.
WebFlux application의 context에서 Servlet filter를 mapping하거나 Servlet API를 직접 조작하는 것은 강력히 권장되지 않습니다. 위에 나열된 이유로, 동일한 context에서 blocking I/O와 non-blocking I/O를 혼합하면 runtime issue가 발생합니다.
Performance에는 많은 특성과 의미가 있습니다. Reactive와 non-blocking이 일반적으로 application을 더 빠르게 만들지는 않습니다. 예를 들어 WebClient를 사용하여 원격 호출을 병렬로 수행하는 경우와 같이 몇몇 경우에는 그렇게 될 수 있습니다.
그러나 non-blocking 방식으로 작업을 수행하려면 더 많은 작업이 필요하며, 이는 필요한 processing time을 약간 증가시킬 수 있습니다.
Reactive와 non-blocking의 핵심 기대 이점은 적은 수의 고정된 thread와 더 적은 memory로 scale 할 수 있는 능력입니다. 이는 application이 더 예측 가능한 방식으로 scale 하기 때문에 부하 하에서 더 탄력적으로 만듭니다. 그러나 이러한 이점을 관찰하려면 어떤 latency(느리고 예측 불가능한 network I/O의 조합 포함)가 필요합니다. 바로 그 지점에서 reactive stack이 강점을 발휘하기 시작하며, 차이는 극적일 수 있습니다.
Spring MVC와 Spring WebFlux는 모두 annotated controller를 지원하지만, concurrency model과 blocking 및 thread에 대한 기본 가정에는 중요한 차이가 있습니다.
Spring MVC(및 일반적인 servlet application)에서는 application이 현재 thread를 block할 수 있다고 가정합니다(예를 들어 원격 호출의 경우). 이러한 이유로 servlet container는 request handling 중 발생할 수 있는 blocking을 흡수하기 위해 큰 thread pool을 사용합니다.
Spring WebFlux(및 일반적인 non-blocking server)에서는 application이 block하지 않는다고 가정합니다. 따라서 non-blocking server는 request를 처리하기 위해 작은 고정 크기 thread pool(event loop worker)을 사용합니다.
“To scale”과 “small number of threads”는 모순적으로 들릴 수 있지만, 현재 thread를 절대 block하지 않고(대신 callback에 의존하는 것)은 blocking call을 흡수하기 위한 추가 thread가 필요 없다는 것을 의미합니다.
Blocking library를 사용해야 한다면 어떻게 해야 할까요? Reactor와 RxJava는 모두 publishOn operator를 제공하여 다른 thread에서 processing을 계속할 수 있게 합니다.
이는 손쉽게 빠져나갈 수 있는 통로가 있다는 뜻입니다. 그러나 blocking API는 이 concurrency model에 잘 맞지 않는다는 점을 명심하십시오.
Reactor와 RxJava에서는 operator를 통해 logic을 선언합니다. runtime 시점에는 data가 구분된 단계에서 순차적으로 처리되는 reactive pipeline이 형성됩니다. 이의 핵심 이점은 application code가 pipeline 내에서 동시에 호출되지 않기 때문에 mutable state를 보호해야 할 필요로부터 application을 해방시킨다는 점입니다.
Spring WebFlux로 실행 중인 server에서 어떤 thread를 기대할 수 있을까요?
“vanilla” Spring WebFlux server(예: data access나 기타 optional dependency가 없는 경우)에서는 server를 위한 하나의 thread와 request 처리용으로 몇 개의 다른 thread(일반적으로 CPU core 수만큼)를 기대할 수 있습니다. 그러나 servlet container는 servlet(blocking) I/O와 servlet 3.1(non-blocking) I/O 사용을 모두 지원하기 위해 더 많은 thread(예: Tomcat에서 10개)로 시작할 수 있습니다.
Reactive WebClient는 event loop style로 동작합니다. 따라서 이에 관련된 소수의 고정된 processing thread(예: Reactor Netty connector와 함께 사용하는 reactor-http-nio-)를 볼 수 있습니다. 그러나 Reactor Netty가 client와 server 모두에 사용되는 경우, 둘은 기본적으로 event loop resource를 공유합니다.
Reactor와 RxJava는 다른 thread pool로 processing을 전환하는 데 사용되는 publishOn operator와 함께 사용할 thread pool abstraction을 제공하며, 이를 scheduler라고 부릅니다.
Scheduler는 특정 concurrency strategy를 암시하는 이름을 가지고 있습니다. 예를 들어 “parallel”(제한된 수의 thread를 사용하는 CPU-bound 작업용) 또는 “elastic”(많은 수의 thread를 사용하는 I/O-bound 작업용) 등이 있습니다. 이러한 thread를 본다면, 이는 어떤 code가 특정 thread pool Scheduler strategy를 사용하고 있다는 뜻입니다.
Data access library 및 기타 third party dependency도 자체적으로 thread를 생성하고 사용할 수 있습니다.
Spring Framework는 servers를 시작하고 중지하는 support를 제공하지 않습니다. server의 threading model을 구성하려면 server별 configuration API를 사용해야 하며, Spring Boot를 사용하는 경우 각 server에 대한 Spring Boot configuration option을 확인해야 합니다.
WebClient는 직접 configure할 수 있습니다.
그 외의 모든 library에 대해서는 해당 문서를 참조하십시오.
Spring WebFlux
Reactive Core