此版本仍在开发中,尚未被视为稳定版本。最新的稳定版本请使用 Spring Framework 6.1.13! |
此版本仍在开发中,尚未被视为稳定版本。最新的稳定版本请使用 Spring Framework 6.1.13! |
为什么创建 Spring WebFlux?
部分答案是需要一个非阻塞 Web 堆栈来处理并发
线程数量少,硬件资源较少。Servlet 非阻塞 I/O
引出 Servlet API 的其余部分,其中 Contract 是同步的
(, ) 或阻止 (, )。这就是动机
作为任何非阻塞运行时的基础。那是
重要,因为服务器(比如 Netty)在 async 中已经很成熟了,
非阻塞空间。Filter
Servlet
getParameter
getPart
答案的另一部分是函数式编程。就像添加注释一样
在 Java 5 中创建的机会(例如带注释的 REST 控制器或单元测试)中,
在 Java 8 中添加 lambda 表达式为 Java 中的功能性 API 创造了机会。
这对于非阻塞应用程序和延续式 API(如流行的
by 和 ReactiveX),它们允许声明式
异步逻辑的组成。在编程模型级别,Java 8 启用了 Spring
WebFlux 提供功能性 Web 端点以及带注释的控制器。CompletableFuture
定义 “Reactive”
我们谈到了“非阻塞”和“函数式”,但响应式是什么意思?
术语“反应式”是指围绕响应变化而构建的编程模型 — 网络组件对 I/O 事件做出反应,UI 控制器对鼠标事件做出反应,等等。 从这个意义上说,非阻塞是反应性的,因为我们现在处于模式中,而不是被阻塞 在操作完成或数据可用时对通知做出反应。
我们 Spring 团队还有另一个重要的机制与 “reactive” 相关联 那就是非阻塞背压。在同步的命令式代码中,阻塞调用 作为一种自然形式的背压,迫使呼叫者等待。在非阻塞 代码中,控制事件的速率变得很重要,这样快速生产者就不会 压倒它的目的地。
Reactive Streams 是一个小规范(在 Java 9 中也采用了) 它定义了异步组件与背压之间的交互。 例如,数据存储库(充当 Publisher) 可以生成 HTTP 服务器(充当订阅者)的数据 然后可以写入响应。Reactive Streams 的主要目的是让 subscriber 控制发布服务器生成数据的速度或速度。
常见问题:如果出版商不能放慢速度怎么办? Reactive Streams 的目的只是建立机制和边界。 如果发布者无法放慢速度,则必须决定是缓冲、丢弃还是失败。 |
常见问题:如果出版商不能放慢速度怎么办? Reactive Streams 的目的只是建立机制和边界。 如果发布者无法放慢速度,则必须决定是缓冲、丢弃还是失败。 |
反应式 API
Reactive Streams 在互操作性方面起着重要作用。图书馆对此感兴趣
和基础设施组件,但作为应用程序 API 的用处不大,因为它太
低级。应用程序需要更高级别、更丰富的功能 API 来
compose async logic — 类似于 Java 8 API,但不仅适用于集合。
这就是响应式库所扮演的角色。Stream
Reactor 是
Spring WebFlux 的 Web Flux 中。它提供 Mono
和 Flux
API 类型
通过一组丰富的运算符来处理 0..1 () 和 0..N () 的数据序列,这些运算符与
运算符的 ReactiveX 词汇表。
Reactor 是一个 Reactive Streams 库,因此,它的所有运算符都支持非阻塞背压。
Reactor 非常注重服务器端 Java。它是密切合作开发的
与Spring。Mono
Flux
WebFlux 需要 Reactor 作为核心依赖项,但它可以与其他反应式
库。作为一般规则,WebFlux API 接受 plain 作为 input,在内部将其适应 Reactor 类型,使用它,并返回 a 或 a 作为输出。因此,您可以将 any 作为输入传递,并且可以应用
操作,但您需要调整输出以用于另一个反应式库。
只要可行(例如,带注释的控制器),WebFlux 就会透明地适应
RxJava 或其他反应式库。有关更多详细信息,请参阅 Reactive Libraries 。Publisher
Flux
Mono
Publisher
除了反应式 API 之外,WebFlux 还可以与 Kotlin 中的协程 API 一起使用,后者提供了一种更加命令式的编程风格。 以下 Kotlin 代码示例将随协程 API 一起提供。 |
除了反应式 API 之外,WebFlux 还可以与 Kotlin 中的协程 API 一起使用,后者提供了一种更加命令式的编程风格。 以下 Kotlin 代码示例将随协程 API 一起提供。 |
编程模型
该模块包含作为 Spring WebFlux 基础的反应式基础,
包括 HTTP 抽象、支持的 Reactive Streams 适配器
服务器、编解码器和核心 WebHandler
API 可与
Servlet API,但具有非阻塞 Contract。spring-web
在此基础上, Spring WebFlux 提供了两种编程模型的选择:
-
带注解的控制器:与 Spring MVC 一致,并基于相同的注解 从模块中。Spring MVC 和 WebFlux 控制器都支持反应式 (Reactor 和 RxJava)返回类型,因此,很难区分它们。一个值得注意的 不同之处在于 WebFlux 还支持反应式参数。
spring-web
@RequestBody
-
[webflux-fn]:基于 Lambda 的轻量级函数式编程模型。您可以想到 this 作为应用程序可用于路由和 处理请求。带注释控制器的最大区别在于应用程序 负责从头到尾的请求处理,而不是通过 注释和被回调。
适用性
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 应用程序,请尝试 reactive 。 您可以返回反应式类型(Reactor、RxJava 或其他) 直接从 Spring MVC 控制器方法。每次调用的延迟较大或 调用之间的相互依赖关系,好处就越显著。Spring MVC 控制器 也可以调用其他响应式组件。
WebClient
-
如果你有一个大型团队,请记住,在向非阻塞的转变中,学习曲线很陡峭。 函数式编程和声明式编程。无需完全切换即可开始的实用方法 是使用响应式 .除此之外,从小处着手并衡量收益。 我们预计,对于广泛的应用程序,这种转变是不必要的。如果你是 不确定要寻找什么好处,请先了解非阻塞 I/O 的工作原理 (例如,单线程 Node.js) 上的并发性)及其影响。
WebClient
服务器
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。
强烈建议不要在 WebFlux 应用程序的上下文中映射 Servlet 过滤器或直接操作 Servlet API。 由于上面列出的原因,在同一上下文中混合使用阻塞 I/O 和非阻塞 I/O 将导致运行时问题。 |
性能
性能具有许多特征和含义。通常是反应式和非阻塞的
不要使应用程序运行得更快。在某些情况下,它们可以 - 例如,如果使用 来并行运行远程调用。但是,它需要更多的工作来完成
事情以非阻塞方式进行,这可能会略微增加所需的处理时间。WebClient
响应式和非阻塞性的主要预期好处是能够使用小型 固定线程数和较少的内存。这使得应用程序在负载下更具弹性, 因为它们以更可预测的方式扩展。但是,为了观察这些好处,您需要 需要有一些延迟(包括缓慢且不可预测的网络 I/O 的组合)。 这就是响应式堆栈开始显示其优势的地方,差异可能是 戏剧性的。
并发模型
Spring MVC 和 Spring WebFlux 都支持带注释的控制器,但有一个键 并发模型以及阻塞和线程的默认假设的差异。
在 Spring MVC(以及一般的 servlet 应用程序)中,假定应用程序可以 阻止当前线程(例如,用于远程调用)。因此,Servlet 容器 使用大型线程池来吸收请求处理过程中的潜在阻塞。
在 Spring WebFlux(以及一般的非阻塞服务器)中,假定应用程序 不要阻止。因此,非阻塞服务器使用小型的固定大小的线程池 (事件循环工作程序)来处理请求。
“To scale” 和 “small number of thread” 听起来可能自相矛盾,但永远不要阻止 current thread (并依赖于回调) 意味着您不需要额外的线程,因为 没有阻塞调用需要吸收。 |
调用阻塞 API
如果您确实需要使用阻塞库怎么办?Reactor 和 RxJava 都提供了在不同的线程上继续处理的运算符。这意味着有一个
轻松逃生舱口。但请记住,阻止 API 并不适合
this concurrency model 的publishOn
可变状态
在 Reactor 和 RxJava 中,您可以通过运算符声明逻辑。在运行时,响应式 在不同的阶段按顺序处理数据的地方形成管道。主要优势 这是因为它使应用程序不必保护可变状态,因为 该管道中的应用程序代码永远不会并发调用。
线程模型
您应该期望在运行 Spring WebFlux 的服务器上看到哪些线程?
-
在 “vanilla” Spring WebFlux 服务器上(例如,没有数据访问或其他可选 dependencies),您可以期望服务器有一个线程,而 request 则有其他几个线程 处理(通常与 CPU 内核的数量一样多)。但是,Servlet 容器 可以从更多线程开始(例如,Tomcat 上的 10 个),以支持 servlet(阻塞)I/O 以及 servlet 3.1(非阻塞)I/O 使用情况。
-
reactive 以事件循环方式运行。所以你可以看到一个小的、固定的 与该相关的处理线程数(例如,使用 Reactor Netty 连接器)。但是,如果 Reactor Netty 同时用于客户端和服务器,则这两个 默认情况下共享事件循环资源。
WebClient
reactor-http-nio-
-
Reactor 和 RxJava 提供了线程池抽象,称为调度程序,用于用于将处理切换到其他线程池的运算符。 计划程序的名称表示特定的并发策略,例如,“parallel” (对于具有有限线程数的 CPU 绑定工作)或 “elastic” (对于 I/O 绑定工作 大量线程)。如果您看到这样的线程,则表示某些代码正在使用 特定的线程池策略。
publishOn
Scheduler
-
数据访问库和其他第三方依赖项也可以创建和使用线程 他们自己的。
“To scale” 和 “small number of thread” 听起来可能自相矛盾,但永远不要阻止 current thread (并依赖于回调) 意味着您不需要额外的线程,因为 没有阻塞调用需要吸收。 |