为什么要创建 Spring WebFlux?

部分答案是需要一个非阻塞的 Web 堆栈来处理并发性 线程数少,硬件资源少。Servlet 非阻塞 I/O 远离 Servlet API 的其余部分,其中合约是同步的 (, ) 或阻塞 (, )。这就是动机 新的通用 API 可作为任何非阻塞运行时的基础。那是 很重要,因为服务器(如 Netty)在异步中已经建立得很好, 非阻塞空间。FilterServletgetParametergetPart

答案的另一部分是函数式编程。就像添加注释一样 在 Java 5 中创建了机会(例如带注释的 REST 控制器或单元测试),则 在 Java 8 中添加 lambda 表达式为 Java 中的函数式 API 创造了机会。 这对于非阻塞应用程序和延续式 API(如普及)来说是一个福音 by 和 ReactiveX),允许声明性 异步逻辑的组成。在编程模型级别,Java 8 启用了 Spring WebFlux 提供功能性 Web 端点和带注释的控制器。CompletableFuture

定义“反应式”

我们谈到了“非阻塞”和“功能”,但反应式是什么意思?

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

在Spring团队中,还有另一个重要的机制与“反应”相关联 这就是非阻塞背压。在同步命令式代码中,阻止调用 作为一种自然形式的背压,迫使呼叫者等待。在非阻塞中 代码,控制事件的速率变得很重要,这样快速生产者就不会 压倒它的目的地。

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

常见问题:如果出版商不能放慢速度怎么办?
反应式流的目的只是建立机制和边界。 如果发布者无法放慢速度,则必须决定是缓冲、删除还是失败。

反应式 API

响应式流在互操作性方面发挥着重要作用。图书馆对此很感兴趣 和基础设施组件,但作为应用程序 API 的用处较小,因为它太 低级。应用程序需要更高级别、更丰富的功能 API 来 编写异步逻辑 — 类似于 Java 8 API,但不仅适用于集合。 这就是响应式库所扮演的角色。Stream

Reactor 是 Spring WebFlux 中。它提供了 MonoFlux API 类型 通过一组丰富的运算符处理 0..1 () 和 0..N () 的数据序列,这些运算符与 运算符的 ReactiveX 词汇表。 Reactor 是一个反应流库,因此,其所有操作员都支持非阻塞背压。 Reactor 非常注重服务器端 Java。它是在密切合作下开发的 与春天。MonoFlux

WebFlux 需要 Reactor 作为核心依赖项,但它可以与其他反应式 通过 Reactive Streams 的库。作为一般规则,WebFlux API 接受 plain 作为输入,在内部将其调整为 Reactor 类型,使用它,并返回 a 或 a 作为输出。因此,您可以将任何作为输入传递,并且可以申请 对输出进行操作,但您需要调整输出以与另一个响应式库一起使用。 只要可行(例如,带注释的控制器),WebFlux 就会透明地适应使用 RxJava 或其他响应式库。有关详细信息,请参阅响应式库PublisherFluxMonoPublisher

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

编程模型

该模块包含 Spring WebFlux 的基础反应式基础, 包括 HTTP 抽象、支持的 Reactive Streams 适配器 服务器、编解码器和核心 WebHandler API 可与 Servlet API,但具有非阻塞协定。spring-web

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

  • 带注释的控制器:与 Spring MVC 一致,并基于相同的注释 从模块。Spring MVC 和 WebFlux 控制器都支持响应式 (Reactor 和 RxJava) 返回类型,因此,区分它们并不容易。一个值得注意的 不同的是,WebFlux 还支持响应式参数。spring-web@RequestBody

  • [webflux-fn]:基于 Lambda 的轻量级函数式编程模型。你可以想到 这是一个小型库或一组实用程序,应用程序可以使用这些库来路由 和 处理请求。与带注释的控制器的最大区别在于应用程序 负责从头到尾的请求处理,而不是通过声明意图 注释和被召回。

适用性

Spring MVC 还是 WebFlux?

这是一个很自然的问题,但却建立了一个不合理的二分法。实际上,两者都 共同扩大可用选项的范围。两者专为 彼此的连续性和一致性,它们可以并排使用,并提供反馈 从每一方来看,双方都受益。下图显示了两者之间的关系,它们之间的关系 有共同点,以及每个独特支持的内容:

Spring MVC 和 Webflux VENN

我们建议您考虑以下具体几点:

  • 如果您有一个运行良好的 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 是最佳选择 至少对于普通架构。在技术上,反应堆和 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 启动器,可以自动执行这些步骤。默认情况下,启动器使用 Netty,但通过更改您的 Tomcat、Jetty 或 Undertow 很容易切换到 Tomcat、Jetty 或 Undertow 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 都为操作员提供了在不同的线程上继续处理的操作。这意味着有一个 轻松逃生舱口。但是请记住,阻塞 API 并不适合 此并发模型。publishOn

可变状态

在 Reactor 和 RxJava 中,您可以通过运算符声明逻辑。在运行时,反应式 管道是在不同阶段按顺序处理数据的地方形成的。主要优势 这是因为它使应用程序不必保护可变状态,因为 该管道中的应用程序代码永远不会并发调用。

线程模型

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

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

  • 反应式以事件循环方式运行。所以你可以看到一个小的,固定的 与此相关的处理线程数(例如,使用 Reactor Netty 连接器)。但是,如果 Reactor Netty 同时用于客户端和服务器,则两者 默认情况下共享事件循环资源。WebClientreactor-http-nio-

  • Reactor 和 RxJava 提供了线程池抽象,称为调度程序,用于将处理切换到其他线程池的运算符。 调度程序的名称建议特定的并发策略,例如“并行” (适用于线程数有限的 CPU 密集型工作)或“弹性”(适用于 I/O 密集型工作) 大量线程)。如果您看到这样的线程,则意味着某些代码正在使用 特定的线程池策略。publishOnScheduler

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

配置

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