Overview

Spring WebFlux 是为何而创建?

Why was Spring WebFlux created?

答案的一部分是对非阻塞 Web 栈的需求,以便使用少量线程处理并发,并使用较少的硬件资源进行扩展。Servlet 非阻塞 I/O 引导离开了其它的 Servlet API,其中协定是同步的(FilterServlet)或阻塞的(getParametergetPart)。这是采用新的通用 API 作为任何非阻塞运行时的基础的动机。这十分重要,因为在异步、非阻塞领域中已充分确立了诸如 Netty 之类的服务器。

Part of the answer is the need for a non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources. Servlet non-blocking I/O leads away from the rest of the Servlet API, where contracts are synchronous (Filter, Servlet) or blocking (getParameter, getPart). This was the motivation for a new common API to serve as a foundation across any non-blocking runtime. That is important because of servers (such as Netty) that are well-established in the async, non-blocking space.

答案的另一部分是函数式编程。就像在 Java 5 中添加注释所创造的机会(例如带有注释的 REST 控制器或单元测试)一样,在 Java 8 中添加 lambda 表达式为 Java 中的函数式 API 创造了机会。这有利于无阻塞应用程序和延续式 API(由 `CompletableFuture`和 ReactiveX推广),这些 API 允许声明式异步逻辑组合。在编程模型层面,Java 8 允许 Spring WebFlux 除了带有注释的控制器外,还可以提供函数式 Web 端点。

The other part of the answer is functional programming. Much as 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 created opportunities for functional APIs in Java. This is a boon for non-blocking applications and continuation-style APIs (as popularized by CompletableFuture and ReactiveX) that allow declarative composition of asynchronous logic. At the programming-model level, Java 8 enabled Spring WebFlux to offer functional web endpoints alongside annotated controllers.

Define “Reactive”

我们接触到了“non-blocking”和“functional”,但响应式意味着什么?

We touched on “non-blocking” and “functional” but what does reactive mean?

术语“reactive”指的是围绕对变更作出反应而构建的编程模型——网络组件对 I/O 事件做出反应、UI 控制器对鼠标事件做出反应等等。从这个意义上讲,非阻塞是响应式的,因为我们现在不在受阻的状态中,而是处于对已完成的操作或已可用数据的通知作出反应的状态。

The term, “reactive,” refers to programming models that are built around reacting to change — network components reacting to I/O events, UI controllers reacting to mouse events, and others. In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available.

还有另一种重要的机制,Spring 团队将其与“reactive”相关联,那就是非阻塞反压。在同步命令式代码中,阻塞调用作为一种自然的反压形式,迫使调用者等待。在非阻塞代码中,控制事件速率以使快速生产者不过度占据其目标变得很重要。

There is also another important mechanism that we on the Spring team associate with “reactive” and that is non-blocking back pressure. In synchronous, imperative code, blocking calls serve as a natural form of back pressure that forces the caller to wait. In non-blocking code, it becomes important to control the rate of events so that a fast producer does not overwhelm its destination.

Reactive Streams 是一个 小规范(在 Java 9 中也 采用),该规范定义了异步组件与反压之间的交互。例如,一个数据存储库(担任 发布者)可以生成数据,然后 HTTP 服务器(担任 订阅者)可以将数据写入响应。Reactive Streams 的主要目的是让订阅者控制发布者生成数据的速度。

Reactive Streams is a small spec (also adopted in Java 9) that defines the interaction between asynchronous components with back pressure. For example a data repository (acting as Publisher) can produce data that an HTTP server (acting as Subscriber) can then write to the response. The main purpose of Reactive Streams is to let the subscriber control how quickly or how slowly the publisher produces data.

Common question: what if a publisher cannot slow down? 反向流的目的仅在于建立机制和边界。如果发布者无法减慢速度,则必须决定是缓冲、丢弃还是失败。

Common question: what if a publisher cannot slow down? The purpose of Reactive Streams is only to establish the mechanism and a boundary. If a publisher cannot slow down, it has to decide whether to buffer, drop, or fail.

Reactive API

Reactive Streams 在互操作性方面发挥着重要作用。它对库和基础设施组件很有用,但不太适合作为应用程序 API,因为它过于{低级}。应用程序需要一个更高级且更丰富的函数式 API 来组合异步逻辑,类似于 Java 8 Stream API,但不仅限于集合。这就是响应式库所发挥的作用。

Reactive Streams plays an important role for interoperability. It is of interest to libraries and infrastructure components but less useful as an application API, because it is too low-level. Applications need a higher-level and richer, functional API to compose async logic — similar to the Java 8 Stream API but not only for collections. This is the role that reactive libraries play.

Reactor 是 Spring WebFlux 的首选反应式库。它提供了 MonoFlux API 类型,通过一组丰富的操作符处理 0..1 (Mono) 和 0..N (Flux) 的数据序列,这些操作符与 ReactiveX [ vocabulary of operators] 保持一致。Reactor 是一个 Reactive Streams 库,因此它的所有操作符都支持非阻塞反压。Reactor 非常注重服务器端的 Java。它是在与 Spring 的密切合作下开发的。

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

WebFlux 需要 Reactor 作为核心依赖项,但它可以通过 Reactive Streams 与其他反应式库互操作。作为一般规则,WebFlux API 接受一个普通的 `Publisher`作为输入,在内部将其调整为 Reactor 类型,使用它,并返回一个 `Flux`或 `Mono`作为输出。因此,你可以将任何 `Publisher`作为输入 传递,并且可以在输出上应用操作,但你需要调整输出以与另一反应式库结合使用。如果可行(例如带有注释的控制器),WebFlux 会以透明方式调整 RxJava 或其他反应式库的使用。请参阅 Reactive Libraries以了解更多详细信息。

WebFlux requires Reactor as a core dependency but it is interoperable with other reactive libraries via Reactive Streams. As a general rule, a WebFlux API accepts a plain Publisher as input, adapts it to a Reactor type internally, uses that, and returns either a Flux or a Mono as output. So, you can pass any Publisher as input and you can apply operations on the output, but you need to adapt the output for use with another reactive library. Whenever feasible (for example, annotated controllers), WebFlux adapts transparently to the use of RxJava or another reactive library. See Reactive Libraries for more details.

除了响应式 API,WebFlux 还可以与 Kotlin 中的 Coroutines API 一起使用,后者提供了更命令式的编程风格。以下 Kotlin 代码示例将随 Coroutines API 一起提供。

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

Programming Models

spring-web`模块包含 Spring WebFlux 的基础反应式框架,包括 HTTP 抽象、支持的服务器的 Reactive Streams adapterscodecs以及与 Servlet API 相当但具有无阻塞契约的核心 `WebHandler API

The spring-web module contains the reactive foundation that underlies Spring WebFlux, including HTTP abstractions, Reactive Streams adapters for supported servers, codecs, and a core WebHandler API comparable to the Servlet API but with non-blocking contracts.

在该基础上,Spring WebFlux 提供了两种编程模型可供选择:

On that foundation, Spring WebFlux provides a choice of two programming models:

  • Annotated Controllers: Consistent with Spring MVC and based on the same annotations from the spring-web module. Both Spring MVC and WebFlux controllers support reactive (Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable difference is that WebFlux also supports reactive @RequestBody arguments.

  • [webflux-fn]: Lambda-based, lightweight, and functional programming model. You can think of this as a small library or a set of utilities that an application can use to route and handle requests. The big difference with annotated controllers is that the application is in charge of request handling from start to finish versus declaring intent through annotations and being called back.

Applicability

Spring MVC 还是 WebFlux?

Spring MVC or WebFlux?

这是一个很自然的问题,但它设置了一个不合理的二分法。实际上,两者的结合扩大了可用选项的范围。这两者旨在实现连续性和一致性,两者并排可用,并且彼此的反馈会使双方受益。下图显示了两者的关系、两者共有的内容以及两者分别支持的独特内容:

A natural question to ask but one that sets up an unsound dichotomy. Actually, both work together to expand the range of available options. The two are designed for continuity and consistency with each other, they are available side by side, and feedback from each side benefits both sides. The following diagram shows how the two relate, what they have in common, and what each supports uniquely:

spring mvc and webflux venn

我们建议你考虑以下具体要点:

We suggest that you consider the following specific points:

  • If you have a Spring MVC application that works fine, there is no need to change. Imperative programming is the easiest way to write, understand, and debug code. You have maximum choice of libraries, since, historically, most are blocking.

  • If you are already shopping for a non-blocking web stack, Spring WebFlux offers the same execution model benefits as others in this space and also provides a choice of servers (Netty, Tomcat, Jetty, Undertow, and Servlet containers), a choice of programming models (annotated controllers and functional web endpoints), and a choice of reactive libraries (Reactor, RxJava, or other).

  • If you are interested in a lightweight, functional web framework for use with Java 8 lambdas or Kotlin, you can use the Spring WebFlux functional web endpoints. That can also be a good choice for smaller applications or microservices with less complex requirements that can benefit from greater transparency and control.

  • In a microservice architecture, you can have a mix of applications with either Spring MVC or Spring WebFlux controllers or with Spring WebFlux functional endpoints. Having support for the same annotation-based programming model in both frameworks makes it easier to re-use knowledge while also selecting the right tool for the right job.

  • A simple way to evaluate an application is to check its dependencies. If you have blocking persistence APIs (JPA, JDBC) or networking APIs to use, Spring MVC is the best choice for common architectures at least. It is technically feasible with both Reactor and RxJava to perform blocking calls on a separate thread but you would not be making the most of a non-blocking web stack.

  • If you have a Spring MVC application with calls to remote services, try the reactive WebClient. You can return reactive types (Reactor, RxJava, or other) directly from Spring MVC controller methods. The greater the latency per call or the interdependency among calls, the more dramatic the benefits. Spring MVC controllers can call other reactive components too.

  • If you have a large team, keep in mind the steep learning curve in the shift to non-blocking, functional, and declarative programming. A practical way to start without a full switch is to use the reactive WebClient. Beyond that, start small and measure the benefits. We expect that, for a wide range of applications, the shift is unnecessary. If you are unsure what benefits to look for, start by learning about how non-blocking I/O works (for example, concurrency on single-threaded Node.js) and its effects.

Servers

Spring WebFlux 在 Tomcat、Jetty、Servlet 容器以及 Netty 和 Undertow 等非 Servlet 运行时上得到支持。所有服务器都调整为一个低层级的 common API,以便可以在各个服务器上支持较高层级的 programming models

Spring WebFlux is supported on Tomcat, Jetty, Servlet containers, as well as on non-Servlet runtimes such as Netty and Undertow. All servers are adapted to a low-level, common API so that higher-level programming models can be supported across servers.

Spring WebFlux 没有内置支持来启动或停止服务器。但是,可以轻松地使用 Spring 配置 assemble一个应用程序,并且使用几行代码 WebFlux infrastructurerun it该应用程序。

Spring WebFlux does not have built-in support to start or stop a server. However, it is easy to assemble an application from Spring configuration and WebFlux infrastructure and run it with a few lines of code.

Spring Boot 有一个 WebFlux starter 可以自动执行这些步骤。默认情况下,starter 使用 Netty,但通过更改你的 Maven 或 Gradle 依赖项,可以轻松地切换到 Tomcat、Jetty 或 Undertow。Spring Boot 默认使用 Netty,因为它在异步非阻塞领域使用更广泛,并允许客户端和服务器共享资源。

Spring Boot has a WebFlux starter that automates these steps. By default, the starter uses Netty, but it is easy to switch to Tomcat, Jetty, or Undertow by changing your Maven or Gradle dependencies. Spring Boot defaults to Netty, because it is more widely used in the asynchronous, non-blocking space and lets a client and a server share resources.

Tomcat 和 Jetty 既可以与 Spring MVC 一起使用,也可以与 WebFlux 一起使用。但是请记住,它们的使用方式大不相同。Spring MVC 依赖于 Servlet 阻塞 I/O,并允许应用程序在需要时直接使用 Servlet API。Spring WebFlux 依赖于 Servlet 非阻塞 I/O,并在一个低级适配器后面使用 Servlet API。它不会公开供直接使用。

Tomcat and Jetty can be used with both Spring MVC and WebFlux. Keep in mind, however, 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 if they need to. Spring WebFlux relies on Servlet non-blocking I/O and uses the Servlet API behind a low-level adapter. It is not exposed for direct use.

强烈建议不要在 WebFlux 应用程序的上下文中映射 Servlet 过滤器或直接操作 Servlet API。由于上述原因,在同一个上下文中混合阻塞 I/O 和非阻塞 I/O 将导致运行时问题。

It is strongly advised not to map Servlet filters or directly manipulate the Servlet API in the context of a WebFlux application. For the reasons listed above, mixing blocking I/O and non-blocking I/O in the same context will cause runtime issues.

对于 Undertow,Spring WebFlux 在没有 Servlet API 的情况下直接使用 Undertow API。

For Undertow, Spring WebFlux uses Undertow APIs directly without the Servlet API.

Performance

性能具有多种特性和含义。响应式和非阻塞通常不会让应用程序运行得更快。在某些情况下,它们可以实现这一点,例如,如果使用 WebClient 并行运行远程调用。但是,采用非阻塞方式做事需要更多的工作,这会略微增加所需的处理时间。

Performance has many characteristics and meanings. Reactive and non-blocking generally do not make applications run faster. They can in some cases – for example, if using the WebClient to run remote calls in parallel. However, it requires more work to do things the non-blocking way, and that can slightly increase the required processing time.

响应式和非阻塞的关键预期优点在于能够使用少量固定的线程和更少的内存进行扩展。这样可以使应用程序在负载下更具有弹性,因为它们可以以更可预测的方式进行扩展。然而,为了观察到这些好处,你需要有一些延迟(包括缓慢且不可预测的网络 I/O 的混合)。这就是响应式堆栈开始显现其优势的地方,并且差异可能很大。

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

Concurrency Model

Spring MVC 和 Spring WebFlux 都支持带注释的控制器,但在并发模型的默认假设和对阻塞和线程的默认假设方面存在一个关键区别。

Both Spring MVC and Spring WebFlux support annotated controllers, but there is a key difference in the concurrency model and the default assumptions for blocking and threads.

在 Spring MVC(以及 servlet 应用程序)中,应用程序可以阻塞当前线程(例如,对于远程调用)。出于这个原因,servlet 容器使用一个大型线程池来吸收请求处理期间可能出现的阻塞。

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

在 Spring WebFlux(以及非阻塞服务器)中,假设应用程序不会阻塞。因此,非阻塞服务器使用一个小型的固定大小的线程池(事件循环工作程序)来处理请求。

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

“To scale” 和 “small number of threads” 听起来可能会自相矛盾,但永远不阻塞当前线程(而是依赖于回调)意味着您不需要额外的线程,因为不需要吸收阻塞调用。

“To scale” and “small number of threads” may sound contradictory, but to never block the current thread (and rely on callbacks instead) means that you do not need extra threads, as there are no blocking calls to absorb.

Invoking a Blocking API

如果确实需要使用一个阻塞程序库呢?Reactor 和 RxJava 都提供了`publishOn`运算符,用于在不同的线程上继续处理。那意味着有一个简单的跳板。然而,请记住,阻塞式 API 并不适合此并发模型。

What if you do need to use a blocking library? Both Reactor and RxJava provide the publishOn operator to continue processing on a different thread. That means there is an easy escape hatch. Keep in mind, however, that blocking APIs are not a good fit for this concurrency model.

Mutable State

在 Reactor 和 RxJava 中,您可以通过运算符来声明逻辑。在运行时,形成一个反应管道,数据在不同的阶段顺序处理。这一个关键性的优点是,它使应用程序不必保护可变状态,因为管道内的应用程序代码永远不会被同时调用。

In Reactor and RxJava, you declare logic through operators. At runtime, a reactive pipeline is formed where data is processed sequentially, in distinct stages. A key benefit of this is that it frees applications from having to protect mutable state because application code within that pipeline is never invoked concurrently.

Threading Model

您期望在运行 Spring WebFlux 的服务器上看到哪些线程?

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

  • On a “vanilla” Spring WebFlux server (for example, no data access or other optional dependencies), you can expect one thread for the server and several others for request processing (typically as many as the number of CPU cores). Servlet containers, however, may start with more threads (for example, 10 on Tomcat), in support of both servlet (blocking) I/O and servlet 3.1 (non-blocking) I/O usage.

  • The reactive WebClient operates in event loop style. So you can see a small, fixed number of processing threads related to that (for example, reactor-http-nio- with the Reactor Netty connector). However, if Reactor Netty is used for both client and server, the two share event loop resources by default.

  • Reactor and RxJava provide thread pool abstractions, called schedulers, to use with the publishOn operator that is used to switch processing to a different thread pool. The schedulers have names that suggest 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 such threads, it means some code is using a specific thread pool Scheduler strategy.

  • Data access libraries and other third party dependencies can also create and use threads of their own.

Configuring

Spring Framework 不提供对启动和停止 servers的支持。若要为服务器配置线程模型,你需要使用特定于服务器的配置 API,或者,如果你使用 Spring Boot,请查看每个服务器的 Spring Boot 配置选项。你可以直接 configureWebClient。对于所有其他库,请参阅其各自的文档。

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