为什么创建 Spring WebFlux?spring-doc.cn

部分答案是需要一个非阻塞 Web 堆栈来处理并发 线程数量少,硬件资源较少。Servlet 非阻塞 I/O 引出 Servlet API 的其余部分,其中 Contract 是同步的 (, ) 或阻止 (, )。这就是动机 作为任何非阻塞运行时的基础。那是 重要,因为服务器(比如 Netty)在 async 中已经很成熟了, 非阻塞空间。FilterServletgetParametergetPartspring-doc.cn

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

定义 “Reactive”

我们谈到了“非阻塞”和“函数式”,但响应式是什么意思?spring-doc.cn

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

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

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

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

反应式 API

Reactive Streams 在互操作性方面起着重要作用。图书馆对此感兴趣 和基础设施组件,但作为应用程序 API 的用处不大,因为它太 低级。应用程序需要更高级别、更丰富的功能 API 来 compose async logic — 类似于 Java 8 API,但不仅适用于集合。 这就是响应式库所扮演的角色。Streamspring-doc.cn

Reactor 是 Spring WebFlux 的 Web Flux 中。它提供 MonoFlux API 类型 通过一组丰富的运算符来处理 0..1 () 和 0..N () 的数据序列,这些运算符与 运算符的 ReactiveX 词汇表。 Reactor 是一个 Reactive Streams 库,因此,它的所有运算符都支持非阻塞背压。 Reactor 非常注重服务器端 Java。它是密切合作开发的 与Spring。MonoFluxspring-doc.cn

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

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

编程模型

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

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

  • 带注解的控制器:与 Spring MVC 一致,并基于相同的注解 从模块中。Spring MVC 和 WebFlux 控制器都支持反应式 (Reactor 和 RxJava)返回类型,因此,很难区分它们。一个值得注意的 不同之处在于 WebFlux 还支持反应式参数。spring-web@RequestBodyspring-doc.cn

  • [webflux-fn]:基于 Lambda 的轻量级函数式编程模型。您可以想到 this 作为应用程序可用于路由和 处理请求。带注释控制器的最大区别在于应用程序 负责从头到尾的请求处理,而不是通过 注释和被回调。spring-doc.cn

适用性

Spring MVC 还是 WebFlux?spring-doc.cn

这是一个自然而然的问题,但却建立了一个不合理的二分法。实际上,两者都 共同扩展可用选项的范围。这两者专为 彼此之间的连续性和一致性,它们并排可用,并且提供反馈 从每一方对双方都有利。下图显示了两者之间的关系,它们是什么 具有共同点,并且每个支持的内容都独一无二:spring-doc.cn

Spring MVC 和 WebFlux 维恩

我们建议您考虑以下具体要点:spring-doc.cn

  • 如果你有一个运行良好的 Spring MVC 应用程序,则无需更改。 命令式编程是编写、理解和调试代码的最简单方法。 你有最多的库选择,因为从历史上看,大多数库都是阻塞的。spring-doc.cn

  • 如果您已经在购买非阻塞 Web 堆栈,Spring WebFlux 提供了相同的 执行模型与该领域的其他模型一样具有优势,并且还提供了服务器选择 (Netty、Tomcat、Jetty、Undertow 和 Servlet 容器),编程模型选择 (带注释的控制器和功能性 Web 端点)和反应式库的选择 (Reactor、RxJava 或其他)。spring-doc.cn

  • 如果您对用于 Java 8 lambda 的轻量级、功能性 Web 框架感兴趣 或 Kotlin 中,您可以使用 Spring WebFlux 功能性 Web 端点。那也可能是一个不错的选择 适用于要求不太复杂的小型应用程序或微服务,可受益 来自更高的透明度和控制。spring-doc.cn

  • 在微服务架构中,您可以将应用程序与 Spring MVC 混合使用 或 Spring WebFlux 控制器或具有 Spring WebFlux 功能端点。获得支持 对于两个框架中相同的基于注释的编程模型,可以更轻松地 重用知识,同时为正确的工作选择正确的工具。spring-doc.cn

  • 评估应用程序的一种简单方法是检查其依赖项。如果你有阻塞 持久化 API(JPA、JDBC)或网络 API 使用,Spring MVC 是最好的选择 至少对于常见的架构来说是这样。从 Reactor 和 RxJava 在单独的线程上执行阻塞调用,但您不会将 大多数非阻塞 Web 堆栈。spring-doc.cn

  • 如果您有一个调用远程服务的 Spring MVC 应用程序,请尝试 reactive 。 您可以返回反应式类型(Reactor、RxJava 或其他) 直接从 Spring MVC 控制器方法。每次调用的延迟较大或 调用之间的相互依赖关系,好处就越显著。Spring MVC 控制器 也可以调用其他响应式组件。WebClientspring-doc.cn

  • 如果你有一个大型团队,请记住,在向非阻塞的转变中,学习曲线很陡峭。 函数式编程和声明式编程。无需完全切换即可开始的实用方法 是使用响应式 .除此之外,从小处着手并衡量收益。 我们预计,对于广泛的应用程序,这种转变是不必要的。如果你是 不确定要寻找什么好处,请先了解非阻塞 I/O 的工作原理 (例如,单线程 Node.js) 上的并发性)及其影响。WebClientspring-doc.cn

服务器

Spring WebFlux 在 Tomcat、Jetty、Servlet 容器以及 非 Servlet 运行时,例如 Netty 和 Undertow。所有服务器都适用于低级通用 API,因此可以跨服务器支持更高级别的编程模型spring-doc.cn

Spring WebFlux 没有内置支持来启动或停止服务器。然而,事实确实如此 从 Spring 配置和 WebFlux 基础设施轻松组装应用程序,并使用几个 代码行。spring-doc.cn

Spring Boot 有一个 WebFlux Starters,可以自动执行这些步骤。默认情况下,Starters使用 Netty,但通过更改您的 Maven 或 Gradle 依赖项。Spring Boot 默认为 Netty,因为它更广泛 在异步、非阻塞空间中使用,并允许客户端和服务器共享资源。spring-doc.cn

Tomcat 和 Jetty 可以与 Spring MVC 和 WebFlux 一起使用。但是请记住, 它们的使用方式非常不同。Spring MVC 依赖于 Servlet 阻塞 I/O 和 允许应用程序在需要时直接使用 Servlet API。Spring WebFlux 依赖于 Servlet 非阻塞 I/O,并在底层使用 Servlet API 适配器。它不暴露在外,直接使用。spring-doc.cn

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

对于 Undertow,Spring WebFlux 直接使用 Undertow API,而不使用 Servlet API。spring-doc.cn

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

性能

性能具有许多特征和含义。通常是反应式和非阻塞的 不要使应用程序运行得更快。在某些情况下,它们可以 - 例如,如果使用 来并行运行远程调用。但是,它需要更多的工作来完成 事情以非阻塞方式进行,这可能会略微增加所需的处理时间。WebClientspring-doc.cn

响应式和非阻塞性的主要预期好处是能够使用小型 固定线程数和较少的内存。这使得应用程序在负载下更具弹性, 因为它们以更可预测的方式扩展。但是,为了观察这些好处,您需要 需要有一些延迟(包括缓慢且不可预测的网络 I/O 的组合)。 这就是响应式堆栈开始显示其优势的地方,差异可能是 戏剧性的。spring-doc.cn

并发模型

Spring MVC 和 Spring WebFlux 都支持带注释的控制器,但有一个键 并发模型以及阻塞和线程的默认假设的差异。spring-doc.cn

在 Spring MVC(以及一般的 servlet 应用程序)中,假定应用程序可以 阻止当前线程(例如,用于远程调用)。因此,Servlet 容器 使用大型线程池来吸收请求处理过程中的潜在阻塞。spring-doc.cn

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

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

调用阻塞 API

如果您确实需要使用阻塞库怎么办?Reactor 和 RxJava 都提供了在不同的线程上继续处理的运算符。这意味着有一个 轻松逃生舱口。但请记住,阻止 API 并不适合 this concurrency model 的publishOnspring-doc.cn

可变状态

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

线程模型

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

  • 在 “vanilla” Spring WebFlux 服务器上(例如,没有数据访问或其他可选 dependencies),您可以期望服务器有一个线程,而 request 则有其他几个线程 处理(通常与 CPU 内核的数量一样多)。但是,Servlet 容器 可以从更多线程开始(例如,Tomcat 上的 10 个),以支持 servlet(阻塞)I/O 以及 servlet 3.1(非阻塞)I/O 使用情况。spring-doc.cn

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

  • Reactor 和 RxJava 提供了线程池抽象,称为调度程序,用于用于将处理切换到其他线程池的运算符。 计划程序的名称表示特定的并发策略,例如,“parallel” (对于具有有限线程数的 CPU 绑定工作)或 “elastic” (对于 I/O 绑定工作 大量线程)。如果您看到这样的线程,则表示某些代码正在使用 特定的线程池策略。publishOnSchedulerspring-doc.cn

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

配置

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

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