Detailed explanation of Spring WebFlux

Spring Web MVC, the original web framework included in the Spring framework, was designed specifically for Servlet APIs and Servlet containers. Later, the reactive stack Web framework Spring WebFlux was added in version 5.0. It is completely non-blocking, supports Reactive Streams backpressure, and runs on servers such as Netty, Undertow, and Servlet containers.

Both web frameworks reflect the names of their source modules (spring-webmvc and spring-webflux) and coexist in the Spring framework. Each module is optional. Applications can use one module or the other, or in some cases both — for example, Spring MVC Controller with Reactive WebClient.

1. Overview

Why create Spring WebFlux?

Part of the answer is the need for a non-blocking web stack that can handle concurrency with a small number of threads and scale with fewer hardware resources. Servlet's non-blocking I/O leads to other parts of the Servlet API, where the convention is synchronous (Filter, Servlet) or blocking (getParameter, getPart). This is the motivation for a new common API that serves as a foundation across any non-blocking runtime. This is important because servers (such as Netty) are already very mature in the asynchronous, non-blocking world.

The other part of the answer is functional programming. Just like the addition of annotations in Java 5 created opportunities (such as annotated REST controllers or unit tests), the addition of lambda expressions in Java 8 creates opportunities for functional APIs in Java. This is a boon for non-blocking applications and continuation-style APIs (such as those popularized by CompletableFuture and ReactiveX), which allow declarative composition of asynchronous logic. At the programming model level, Java 8 enables Spring WebFlux to provide functional web endpoints alongside annotated controllers.

2. Define “Reactive” (responsive)

We talked about "non-blocking" and "functional", but what do we mean by reactive?

The term "reactive" refers to a programming model that revolves around reacting to changes—network components reacting to I/O events, UI controllers reacting to mouse events, and others. In this sense, non-blocking is reactive, because instead of being blocked, we are now reacting to notifications when the operation is completed or data is available.

There is another important mechanism that our Spring team associates with "responsiveness", which is non-blocking backpressure. In synchronous, imperative code, blocking calls are a natural form of backpressure, forcing the caller to wait. In non-blocking code, it becomes important to control the speed of events so that a fast producer does not overwhelm its "consumers".

Reactive Streams is a small specification (also adopted in Java 9) that defines interactions between asynchronous components, with backpressure. For example, a data repository (acting as a Publisher) can produce data, and then an HTTP server (acting as a Subscriber) can write responses. The main purpose of Reactive Streams is to allow subscribers to control how quickly the publisher produces data.

Frequently Asked Questions: What if the publisher can't slow down?
The purpose of Reactive Stream is just to establish a mechanism and a boundary. If a publisher cannot slow down, it must decide whether to buffer, give up, or fail.

3. Reactive API

Reactive Streams play an important role in interoperability. It makes sense for libraries and infrastructure components, but is less useful as an API for applications because it's too low-level. Applications need a higher-level, richer, functional API for composing asynchronous logic - similar to Java 8's Stream API, but not just for collections. This is the role of reactive libraries.

Reactor is the preferred reactive library for Spring WebFlux. It provides Mono and Flux API types to operate on data sequences of 0..1 (Mono) and 0..N (Flux) through a rich set of operators consistent with the ReactiveX operator vocabulary. Reactor is a Reactive Streams library, therefore, all its operators support non-blocking backpressure. Reactor has a strong focus on server-side Java. It is developed in close cooperation with Spring.

WebFlux requires Reactor as a core dependency, but it can interoperate with other reactive libraries through Reactive Streams. Generally speaking, the WebFlux API accepts a plain Publisher as input, internally adjusts it to a Reactor type, uses that type, and returns a Flux or Mono as output. So you can pass any Publisher as input and you can apply operations on the output, but you'll need to adapt the output to work with another reactive library. Where feasible (e.g., annotating controllers), WebFlux will transparently adapt to the use of RxJava or other reactive libraries. See Reactive Libraries for more details.

In addition to Reactive APIs, WebFlux can also be used with Coroutines APIs in Kotlin, which provide a more imperative programming style. The following Kotlin code sample will provide Coroutines APIs.

4. Programming model

The spring-web module contains the reactive foundation that powers Spring WebFlux, including HTTP abstractions, server-backed Reactive Streams adapters, codecs, and the core WebHandler API that is comparable to the Servlet API but with non-blocking conventions.

On this basis, Spring WebFlux provides two programming model options:

  • Annotated Controller: Consistent with Spring MVC, based on the same annotations of the spring-web module. Both Spring MVC and WebFlux Controller support reactive (Reactor and RxJava) return types, so it is not easy to distinguish them. An obvious difference is that WebFlux also supports reactive @RequestBody parameters.
  • Functional endpoints: Lambda-based, lightweight, functional programming model. You can think of it as a small library or set of utilities that applications can use to route and handle requests. The biggest difference from annotation-based controllers is that the application is responsible for processing requests from beginning to end, rather than declaring intentions through annotations and being called back.

5. Applicability

Spring MVC or WebFlux?

This is a natural question, but it sets up an unhealthy dichotomy. In fact, both work together to expand the range of available options. Both are designed for continuity and consistency with each other, and they can be used side by side, with feedback from each benefiting both. The diagram below shows the relationship between the two, what they have in common, and what unique features each supports:

We recommend that you consider the following specific points:

  • If you have a Spring MVC application that is running fine, there is no need to change. Imperative programming is the simplest way to write, understand, and debug code. You can maximize your library selection because historically, most libraries have been blocking.
  • If you're already "shopping" for a non-blocking web stack, Spring WebFlux offers the same execution model benefits as others in the space, plus a choice of servers (Netty, Tomcat, Jetty, Undertow, and Servlet containers), programming models (annotated controllers and functional web endpoints), and the choice of reactive libraries (Reactor, RxJava, or others).
  • If you are interested in a lightweight functional web framework for Java 8 lambdas or Kotlin, you can use the Spring WebFlux functional web endpoint. It's also a good option for smaller applications or microservices with less complex requirements that can benefit from greater transparency and control.
  • In a microservices architecture, you can mix your application with Spring MVC or Spring WebFlux controllers or with Spring WebFlux functional endpoints. Supporting the same annotation-based programming model in both frameworks makes it easier to reuse knowledge while also selecting the right tool for the right job.
  • A simple way to evaluate an application is to examine its dependencies. If you have a blocking persistence API (JPA, JDBC) or a network API that you need to use, Spring MVC is at least the best choice for normal architectures. It's technically possible to perform blocking calls on a separate thread with Reactor and RxJava, but you won't take full advantage of a non-blocking web stack.
  • If you have a Spring MVC application that calls a remote service, try reactive WebClient. You can return reactive types (Reactor, RxJava or other) directly from Spring MVC Controller methods. The greater the latency of each call or the greater the interdependence between calls, the greater the benefits. Spring MVC Controller can also call other reactive components.
  • If you have a large team, keep in mind the steep learning curve when moving to non-blocking, functional, and declarative programming. In the absence of a complete conversion, a practical approach is to use reactive WebClient. Beyond that, start small and measure the benefits. We anticipate that for a wide range of applications this transition will not be necessary. If you're not sure what benefits you're looking for, start by understanding how non-blocking I/O works (e.g., concurrency in single-threaded Node.js) and its impact.

6. Server

Spring WebFlux supports Tomcat, Jetty, Servlet containers, and non-Servlet runtimes such as Netty and Undertow. All servers adapt to a low-level common API, allowing higher-level programming models to be supported across servers.

Spring WebFlux has no built-in support for starting or stopping the server. However, it is easy to assemble an application through Spring configuration and WebFlux infrastructure and run it with a few lines of code.

Spring Boot has a WebFlux starter that automates these steps. By default, the starter uses Netty, but you can easily switch to Tomcat, Jetty or Undertow by changing the Maven or Gradle dependencies. Spring Boot uses Netty by default because it is more widely used in the asynchronous, non-blocking space, allowing clients and servers to share resources.

Both Tomcat and Jetty can be used with Spring MVC and WebFlux. However, keep in mind that the way they are used is very different. Spring MVC relies on Servlet blocking I/O and lets applications use the Servlet API directly when needed. Spring WebFlux relies on Servlet's non-blocking I/O and uses the Servlet API behind a low-level adapter. It is not exposed for direct use.

For Undertow, Spring WebFlux uses Undertow's API directly instead of using the Servlet API.

7. Performance

Performance has many characteristics and meanings. Reactive and non-blocking generally don't make applications run faster. In some cases, they can (for example, if you use WebClient to run remote calls in parallel). Overall, doing things the non-blocking way requires more work, which may slightly increase the required processing time.

The key expected benefit of reactive and non-blocking is the ability to scale with a small, fixed number of threads and less memory. This makes applications more resilient under load because they scale in a more predictable way. However, in order to observe these benefits, you need to have some latency (including a mix of slow and unpredictable network I/O). This is where the reactive stack starts to show its advantages, and the difference can be huge.

8. Concurrency model

Both Spring MVC and Spring WebFlux support annotated Controllers, but there is a key difference in the concurrency model and default assumptions about blocking and threading.

In Spring MVC (and servlet applications in general), it is assumed that the application can block the current thread, (for example, for remote calls). For this reason, the servlet container uses a large thread pool to absorb possible blocking during request processing.

In Spring WebFlux (and non-blocking servers in general), it is assumed that the application does not block. Therefore, non-blocking servers use a small, fixed-size thread pool (event loop worke) to handle requests.

"Scale" and "a small number of threads" may sound contradictory, but never blocking the current thread (rather relying on callbacks) means you don't need additional threads because there are no blocking calls to absorb.

1. Call blocking API

What if you really need to use a blocking library? Both Reactor and RxJava provide the publishOn operator to continue processing on different threads. That means there's a simple lifeline. However, keep in mind that blocking APIs are not suitable for this concurrency model.

2. Variable state

In Reactor and RxJava, you declare logic through operators. At runtime, a reactive pipeline is formed in which data is processed sequentially and in stages. A major benefit of doing this is that it relieves the application from having to protect mutable state because the application code in the pipeline will not be called concurrently.

3. Thread model

What threads should you see on a server running with Spring WebFlux?

  • On a "vanilla" Spring WebFlux server (i.e. no data access and no other optional dependencies), you can expect the server to have one thread and several other threads for request processing (usually as many as there are CPU cores). However, the Servlet container may start with more threads (e.g. 10 on Tomcat) to support the use of Servlet (blocking) I/O and Servlet 3.1 (non-blocking) I/O.
  • Reactive WebClient runs in an event loop. So you can see a small number of fixed processing threads associated with it (e.g. reactor-http-nio- and Reactor Netty connector). However, if Reactor Netty is used on both the client and the server, the two share event loop resources by default.
  • Reactor and RxJava provide a thread pool abstraction called a scheduler for use with the publishOn operator for switching processing to a different thread pool. The scheduler's name implies a specific concurrency strategy—for example, "parallel" (for CPU-bound work with a limited number of threads) or "elastic" (for I/O-bound work with a large number of threads). If you see threads like this, it means that some code is using a specific thread pool Scheduler strategy.
  • Data access libraries and other third-party dependencies can also create and use their own threads.

4. Configuration

The Spring framework does not provide support for starting and stopping the server. To configure a server's threading model, you need to use the server-specific configuration API or, if you use Spring Boot, check the Spring Boot configuration options for each server. You can configure WebClient directly. For all other libraries, please see their respective documentation.

Guess you like

Origin blog.csdn.net/leesinbad/article/details/132866083