此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
概述
为什么要创建 Spring WebFlux?
部分答案是需要一个非阻塞 Web 堆栈来处理并发性
线程数少,以更少的硬件资源进行扩展。Servlet 非阻塞 I/O
导致远离 Servlet API 的其余部分,其中合约是同步的
(Filter
,Servlet
) 或阻塞 (getParameter
,getPart
).这就是动机
新的通用 API 作为任何非阻塞运行时的基础。那是
重要,因为服务器(例如 Netty)在异步中已经建立得很好,
无阻塞空间。
答案的另一部分是函数式编程。就像添加注释一样
在 Java 5 创建的机会(例如带注释的 REST 控制器或单元测试)中,该
在 Java 8 中添加 lambda 表达式为 Java 中的函数式 API 创造了机会。
这对于非阻塞应用程序和延续式 API(如普及的那样)来说是一个福音
由CompletableFuture
和 ReactiveX),允许声明式
异步逻辑的组成。在编程模型级别,Java 8 启用了 Spring
WebFlux 提供功能性 Web 端点以及带注释的控制器。
定义“响应式”
我们谈到了“非阻塞”和“功能性”,但响应式是什么意思?
术语“响应式”是指围绕对变化做出反应而构建的编程模型——对 I/O 事件做出反应的网络组件、对鼠标事件做出反应的 UI 控制器等等。 从这个意义上说,非阻塞是被动的,因为我们现在不是被阻塞,而是处于模式 在作完成或数据可用时对通知做出反应。
我们还 Spring 团队将另一个重要机制与“响应式”联系在一起 这就是非阻塞背压。在同步命令式代码中,阻止调用 作为一种自然形式的背压,迫使呼叫者等待。在非阻塞中 代码中,控制事件的速率变得很重要,这样快速生产者就不会 压倒了它的目的地。
Reactive Streams 是一个小规范(在 Java 9 中也采用) 这定义了异步组件与背压之间的交互。 例如,数据存储库(充当发布者) 可以生成 HTTP 服务器(充当订阅者)的数据 然后可以写入响应。Reactive Streams 的主要目的是让 订阅者控制发布者生成数据的速度或速度。
常见问题:如果发布商不能放慢速度怎么办? 反应流的目的只是建立机制和边界。 如果发布者无法减慢速度,则必须决定是缓冲、丢弃还是失败。 |
响应式 API
反应流在互作性方面发挥着重要作用。图书馆对此感兴趣
和基础设施组件,但作为应用程序 API 不太有用,因为它太有用了
低级。应用程序需要更高级别、更丰富的功能 API 来
compose 异步逻辑 — 类似于 Java 8Stream
API,但不仅适用于集合。
这就是响应式库所扮演的角色。
Reactor 是首选的反应性库
Spring WebFlux 的 WebFlux。它提供了Mono
和Flux
API 类型
处理 0..1 (Mono
) 和 0..N (Flux
)通过一组丰富的运算符与
ReactiveX 运算符词汇表。
Reactor 是一个 Reactive Streams 库,因此,它的所有运算符都支持非阻塞背压。
Reactor 非常注重服务器端 Java。它是在密切合作下开发的
与Spring。
WebFlux 需要 Reactor 作为核心依赖项,但它可以与其他响应式互作
通过反应流的库。作为一般规则,WebFlux API 接受普通Publisher
作为输入,在内部将其适配为 Reactor 类型,使用它,并返回Flux
或Mono
作为输出。因此,您可以传递任何Publisher
作为输入,您可以应用
作,但您需要调整输出以与另一个响应式库一起使用。
只要可行(例如,带注释的控制器),WebFlux 就会透明地适应使用
RxJava 或其他响应式库。有关更多详细信息,请参阅响应式库。
除了响应式 API 之外,WebFlux 还可以与 Kotlin 中的协程 API 一起使用,从而提供更命令性的编程风格。 协程 API 将提供以下 Kotlin 代码示例。 |
编程模型
这spring-web
模块包含作为 Spring WebFlux 基础的响应式基础,
包括 HTTP 抽象、支持服务器的响应式流适配器、编解码器和内核WebHandler
应用程序接口可与
Servlet API,但具有非阻塞合约。
在此基础上,Spring WebFlux 提供了两种编程模型的选择:
适用性
Spring MVC 还是 WebFlux?
这是一个很自然的问题,但却设置了一个不健全的二分法。实际上,两者 共同努力,扩大可用选项的范围。两者专为 彼此的连续性和一致性,它们并排可用,并且反馈 双方都受益。下图显示了两者的关系,它们是什么 有共同点,以及每个共同点支持的内容:

我们建议您考虑以下具体要点:
-
如果你有一个运行良好的 Spring MVC 应用程序,则无需更改。 命令式编程是编写、理解和调试代码的最简单方法。 您可以最大限度地选择库,因为从历史上看,大多数库都是阻塞的。
-
如果您已经在购买非阻塞 Web 堆栈,Spring WebFlux 提供相同的功能 执行模型与该领域的其他模型一样受益,并且还提供了服务器的选择 (Netty、Tomcat、Jetty、Undertow 和 Servlet 容器),可选择编程模型 (带注释的控制器和功能性 Web 端点),以及响应式库的选择 (Reactor、RxJava 或其他)。
-
如果您对与 Java 8 lambda 一起使用的轻量级、功能齐全的 Web 框架感兴趣 或 Kotlin,您可以使用 Spring WebFlux 功能 Web 端点。这也是一个不错的选择 适用于要求不太复杂的小型应用程序或微服务,可以受益 来自更大的透明度和控制。
-
在微服务架构中,您可以混合使用具有 Spring MVC 的应用程序 或 Spring WebFlux 控制器或 Spring WebFlux 功能端点。获得支持 对于两个框架中相同的基于注释的编程模型,可以更轻松地 重复使用知识,同时为正确的工作选择正确的工具。
-
评估应用程序的一种简单方法是检查其依赖关系。如果您有阻止 持久化 API(JPA、JDBC)或网络 API 使用,Spring MVC 是最佳选择 至少对于通用架构来说是这样。它在技术上是可行的,无论是 Reactor 还是 RxJava 在单独的线程上执行阻塞调用,但您不会将 大部分是非阻塞 Web 堆栈。
-
如果你有一个调用远程服务的 Spring MVC 应用程序,请尝试响应式
WebClient
. 您可以返回响应式类型(Reactor、RxJava 或其他) 直接来自 Spring MVC 控制器方法。每次调用的延迟越大,或者 呼叫之间的相互依赖性,好处就越显着。Spring MVC 控制器 也可以调用其他响应式组件。 -
如果您有一个庞大的团队,请记住向非阻塞转变的陡峭学习曲线, 函数式编程和声明式编程。无需全开关即可启动的实用方法 就是用响应式
WebClient
.除此之外,从小处着手并衡量收益。 我们预计,对于广泛的应用,这种转变是不必要的。如果你是 不确定要寻找什么好处,请从了解非阻塞 I/O 的工作原理开始 (例如,单线程Node.js上的并发性)及其效果。
服务器
Spring WebFlux 在 Tomcat、Jetty、Servlet 容器以及非 Servlet 运行时(例如 Netty 和 Undertow)上受支持。所有服务器都适应了低级的通用 API,以便可以跨服务器支持更高级别的编程模型。
Spring WebFlux 没有启动或停止服务器的内置支持。但是,它是很容易从 Spring 配置和 WebFlux 基础设施组装应用程序,并用几行代码运行它代码行。
Spring Boot 有一个 WebFlux Starters,可以自动执行这些步骤。默认情况下,Starters使用 Netty,但通过更改 Maven 或 Gradle 依赖项。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 直接使用 Undertow API,而无需 Servlet API。
性能
性能有很多特征和意义。一般响应式和非阻塞性
不要让应用程序运行得更快。在某些情况下,它们可以 - 例如,如果使用WebClient
并行运行远程呼叫。但是,它需要做更多的工作
以非阻塞方式进行,这会稍微增加所需的处理时间。
响应式和非阻塞的关键预期好处是能够通过小的、 固定线程数和更少的内存。这使得应用程序在负载下更具弹性, 因为它们以更可预测的方式扩展。然而,为了观察这些好处,你 需要有一些延迟(包括缓慢和不可预测的网络 I/O 的混合)。 这就是响应式堆栈开始展示其优势的地方,差异可能是 戏剧性的。
并发模型
Spring MVC 和 Spring WebFlux 都支持带注释的控制器,但有一个键 并发模型的差异以及阻塞和线程的默认假设。
在 Spring MVC(以及一般的 servlet 应用程序)中,假设应用程序可以 阻止当前线程(例如,对于远程调用)。因此,servlet 容器 使用大型线程池来吸收请求处理期间的潜在阻塞。
在 Spring WebFlux(以及一般的非阻塞服务器)中,假设应用程序 不要阻塞。因此,非阻塞服务器使用小型、固定大小的线程池 (事件循环工作程序)来处理请求。
“缩放”和“线程数量少”听起来可能很矛盾,但永远不要阻止 当前线程(并依赖回调)意味着您不需要额外的线程,因为 没有要吸收的阻止呼叫。 |
调用阻塞 API
如果您确实需要使用阻止库怎么办?Reactor 和 RxJava 都提供了publishOn
运算符以继续在不同的线程上进行处理。这意味着有一个
轻松逃生舱口。但是请记住,阻止 API 并不适合
这种并发模型。
可变状态
在 Reactor 和 RxJava 中,你通过运算符声明逻辑。在运行时,响应式 管道是在不同阶段按顺序处理数据的地方形成的。一个主要优势 其中,它使应用程序不必保护可变状态,因为 该管道中的应用程序代码永远不会同时调用。
线程模型
您应该期望在运行 Spring WebFlux 的服务器上看到哪些线程?
-
在“普通”Spring WebFlux 服务器上(例如,没有数据访问或其他可选的 dependencies),您可以期望服务器有一个线程,请求需要其他几个线程 处理(通常与 CPU 内核数一样多)。然而,Servlet 容器 可以从更多线程开始(例如,Tomcat 上的 10 个线程),以支持 servlet(阻塞)I/O 和 servlet 3.1(非阻塞)I/O 使用。
-
响应式
WebClient
以事件循环样式运行。所以你可以看到一个小的,固定的 与之相关的处理线程数(例如,reactor-http-nio-
与反应堆 Netty 连接器)。但是,如果 Reactor Netty 同时用于客户端和服务器,则 默认共享事件循环资源。 -
Reactor 和 RxJava 提供了线程池抽象,称为调度器,可与
publishOn
运算符,用于将处理切换到不同的线程池。调度程序的名称建议特定的并发策略——例如,“并行”(对于线程数量有限的 CPU 绑定工作)或“弹性”(用于大量线程的 I/O 绑定工作)。如果您看到此类线程,则意味着某些代码正在使用特定线程池Scheduler
策略。 -
数据访问库和其他第三方依赖项也可以创建和使用线程他们自己的。