Overview

Spring WebFlux 是为何而创建? 答案的一部分是对非阻塞 Web 栈的需求,以便使用少量线程处理并发,并使用较少的硬件资源进行扩展。Servlet 非阻塞 I/O 引导离开了其它的 Servlet API,其中协定是同步的(FilterServlet)或阻塞的(getParametergetPart)。这是采用新的通用 API 作为任何非阻塞运行时的基础的动机。这十分重要,因为在异步、非阻塞领域中已充分确立了诸如 Netty 之类的服务器。 答案的另一部分是函数式编程。就像在 Java 5 中添加注释所创造的机会(例如带有注释的 REST 控制器或单元测试)一样,在 Java 8 中添加 lambda 表达式为 Java 中的函数式 API 创造了机会。这有利于无阻塞应用程序和延续式 API(由 `CompletableFuture`和 ReactiveX推广),这些 API 允许声明式异步逻辑组合。在编程模型层面,Java 8 允许 Spring WebFlux 除了带有注释的控制器外,还可以提供函数式 Web 端点。

Define “Reactive”

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

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

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

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

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

Reactive API

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

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

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

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

Programming Models

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

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

  • Annotated Controllers:与 Spring MVC 一致,且基于 spring-web 模块中的相同注释。Spring MVC 和 WebFlux 控制器均支持响应式(Reactor 和 RxJava)返回类型,因此难以将它们区分开来。一个值得注意的差别在于,WebFlux 还支持响应式 @RequestBody 参数。

  • [webflux-fn]:基于 lambda、轻量级、功能式编程模型。你可以将其视为一个小型库或一组应用程序可用于路由和处理请求的实用程序。与带注释的控制器相比,最大的不同在于应用程序负责从头到尾处理请求,而不是通过注释声明意图并被回调。

Applicability

Spring MVC 还是 WebFlux?

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

spring mvc and webflux venn

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

  • 如果你有一个正常工作的 Spring MVC 应用程序,则无需进行更改。命令式编程是最简单的编写、理解和调试代码的方式。你有最大的库选择,因为历史上大多数都在阻塞。

  • 如果你已经在寻找非阻塞式 Web 栈,则与该领域中的其他工具相比,Spring WebFlux 提供相同​​的执行模型优势,且还提供了服务器的选择(Netty、Tomcat、Jetty、Undertow 和 Servlet 容器)、编程模型(带注释的控制器与功能式 Web 端点)和响应式库(Reactor、RxJava 或其他)。

  • 如果你对用于 Java 8 lambdas 或 Kotlin 的轻量级功能式 Web 框架感兴趣,则可以使用 Spring WebFlux 功能式 Web 端点。对于那些不太复杂且能从更高的透明度和更强的控制中受益的小型应用程序或微服务来说,这也是一个不错的选择。

  • 在微服务架构中,你可以混合使用带有 Spring MVC 或 Spring WebFlux 控制器,或带有 Spring WebFlux 功能式终结点的应用程序。同时在两个框架中支持基于相同注释的编程模型,这使得重用知识变得更加容易,同时也便于为正确的工作选择正确的工具。

  • 评估应用程序的一个简单方法是检查其依赖项。如果你有要使用的阻塞式持久化 API(JPA、JDBC)或网络 API,那么 Spring MVC 是大多数架构的最佳选择,至少是其中的一部分。使用 Reactor 和 RxJava 在单独的线程上执行阻塞式调用在技术上是可行的,但你不会最大程度地利用非阻塞式 Web 栈。

  • 如果你有一个带有对远程服务进行调用的 Spring MVC 应用程序,请尝试使用响应式 WebClient。你可以直接从 Spring MVC 控制器方法返回响应式类型(Reactor、RxJava、or other)。每个调用的延迟时间越长,或调用之间的相互依赖性越大,则好处就越明显。Spring MVC 控制器也可以调用其他响应式组件。

  • 如果你有一个大型团队,请记住向非阻塞式、功能式和声明式编程的转变有一个陡峭的学习曲线。在没有完全切换的情况下开始的一种实用方法是使用响应式 WebClient。除此之外,从小处着手并衡量好处。我们预计,对于各种应用程序来说,这种转变是没有必要的。如果你不确定要寻找什么好处,请从了解非阻塞 I/O 如何工作的基础开始(例如,在单线程 Node.js 上的并行性),及其作用。

Servers

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

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

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

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

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

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

Performance

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

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

Concurrency Model

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

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

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

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

Invoking a Blocking API

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

Mutable State

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

Threading Model

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

  • 在 “vanilla” Spring WebFlux 服务器上(例如,无数据访问或其他可选依赖项),你可以为服务器期望一个线程,并为请求处理期望几个其他线程(通常与 CPU 核心数量一样多)。但是,Servlet 容器可能会从更多线程开始(例如,Tomcat 上的 10 个线程),以支持 servlet(阻塞式)I/O 和 servlet 3.1(非阻塞式)I/O 的使用。

  • 响应式 WebClient 以事件循环方式运行。因此,你可以看到与之相关的小的、固定的处理线程数量(例如,带有 Reactor Netty 连接器的 reactor-http-nio-)。但是,如果 Reactor Netty 用于客户端和服务器,则默认情况下两者共享事件循环资源。

  • Reactor 和 RxJava 提供称为调度器的线程池抽象,用于与 publishOn 运算符一起使用,该运算符用于将处理切换到不同的线程池。调度器的名称暗示了特定的并发策略,例如 “parallel”(用于 CPU 密集型工作,线程数有限)或 “elastic”(用于 I/O 密集型工作,线程数很大)。如果你看到这样的线程,则意味着某些代码正在使用特定的线程池 Scheduler 策略。

  • 数据访问库和其他第三方依赖项也可以创建并使用自己的线程。

Configuring

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