1.1 Overview
为什么创建Spring WebFlux?
答案的一部分是需要一个非阻塞式的Web堆栈来处理少量线程的并发并使用更少的硬件资源进行扩展。 Servlet 3.1确实提供了用于非阻塞I / O的API。但是,使用它会导致Servlet API的其余部分偏离,在Servlet API中,合同是同步的(Filter,Servlet)或阻塞的(getParameter,getPart)。这是促使新的通用API成为所有非阻塞运行时的基础的动机。这很重要,因为服务器(例如Netty)已在异步,非阻塞空间中建立良好。
答案的另一部分是函数式编程。就像在Java 5中添加注解会创造机会(例如带注解的REST控制器或单元测试)一样,在Java 8中添加lambda表达式也会为Java中的功能API创造机会。这对于无阻塞的应用程序和连续样式的API(如由CompletableFuture和ReactiveX普及的API)是有利的,这些API允许以声明方式构成异步逻辑。在编程模型级别,Java 8使Spring WebFlux能够与带注解的控制器一起提供功能性的Web端点。
1.1.1 定义“反应式”
我们谈到了“无障碍”和“功能性”,但是反应式意味着什么?
术语“反应性”是指围绕对更改做出反应的编程模型-网络组件对I / O事件做出反应,UI控制器对鼠标事件做出反应等。从这个意义上说,非阻塞是反应性的,因为随着操作完成或数据可用,我们现在处于响应通知的模式,而不是被阻塞。
我们Spring团队还有另一个重要机制与“反应性”相关联,这是不阻碍背压的机制。在同步命令式代码中,阻塞调用是强制调用者等待的一种自然的背压形式。在非阻塞代码中,控制事件的速率非常重要,这样快速的生产者就不会淹没其目的地。
Reactive Streams是一个小的规范(在Java 9中也采用了),它定义了带有反压力的异步组件之间的交互。例如,数据存储库(充当发布者)可以生成HTTP服务器(充当订阅者)然后可以将其写入响应的数据。 Reactive Streams的主要目的是让订阅者控制发布者生成数据的速度或速度。
常见问题:如果publisher不能放慢脚步怎么办? 反应流的目的仅仅是建立机制和边界。 如果发布者无法放慢速度,则必须决定是缓冲,删除还是失败。
1.1.2 Reactive API
反应流对于互操作性起着重要作用。库和基础结构组件对此很感兴趣,但是由于它太底层了,它作为应用程序API的用处不大。应用程序需要更高级别且功能更丰富的API来构成异步逻辑,这与Java 8 Stream API相似,但不仅适用于集合。这就是反应式库的作用。
Reactor是Spring WebFlux的首选反应库。它提供了Mono和Flux API类型,以通过与ReactiveX运算符词汇对齐的丰富运算符集来处理0..1(Mono)和0..N(Flux)的数据序列。 Reactor是Reactive Streams库,因此,它的所有运算符都支持无阻塞背压。 Reactor非常注重服务器端Java。它是与Spring紧密合作开发的。
WebFlux需要Reactor作为核心依赖项,但是它可以通过Reactive Streams与其他React库进行互操作。通常,WebFlux API接受普通的发布者作为输入,在内部将其适应于Reactor类型,使用它,然后返回Flux或Mono作为输出。因此,您可以将任何发布服务器作为输入传递,并且可以对输出应用操作,但是您需要调整输出以与其他反应式库一起使用。只要可行(例如,带注解的控制器),WebFlux就会透明地适应RxJava或其他反应式库的使用。有关更多详细信息,请参见反应式库。
1.1.3 Programming Models
spring-web模块包含Spring WebFlux基础的反应式基础,包括HTTP抽象,用于支持的服务器的Reactive Streams适配器,编解码器以及与Servlet API相似但具有非阻塞合同的核心WebHandler API。
在此基础上,Spring WebFlux提供了两种编程模型的选择:
带注解的控制器:与Spring MVC一致,并基于来自spring-web模块的相同注解。 Spring MVC和WebFlux控制器都支持反应式(Reactor和RxJava)返回类型,因此,区分它们并不容易。一个显着的区别是WebFlux还支持反应式@RequestBody参数。
功能端点:基于Lambda的轻量级功能编程模型。您可以将其视为一个小型库或一组实用程序,应用程序可以使用它们来路由和处理请求。带注解的控制器的最大区别在于,应用程序负责从头到尾的请求处理,而不是通过注解声明意图并被回调。
1.1.4 Applicability
Spring MVC还是WebFlux?
一个自然的问题要问,但建立了一个不合理的二分法。 实际上,两者共同努力扩大了可用选项的范围。 两者的设计旨在实现彼此的连续性和一致性,它们可以并行使用,并且来自双方的反馈对双方都有利。 下图显示了两者之间的关系,它们的共同点以及各自的独特支持:
我们建议您考虑以下几点:
如果您有运行正常的Spring MVC应用程序,则无需更改。命令式编程是编写,理解和调试代码的最简单方法。您有最大的库选择空间,因为从历史上看,大多数库都处于阻塞状态。
如果您已经在购买无阻塞Web堆栈,那么Spring WebFlux在此空间中提供的执行模型优势与其他模型相同,并且还提供服务器选择(Netty,Tomcat,Jetty,Undertow和Servlet 3.1+容器),选择编程模型(带注解的控制器和功能性Web端点),以及选择反应式库(Reactor,RxJava或其他)。
如果您对与Java 8 lambda或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或其他)。每个呼叫的等待时间或呼叫之间的相互依赖性越大,好处就越明显。 Spring MVC控制器也可以调用其他反应式组件。
如果您有庞大的团队,请牢记向无阻塞,功能性和声明性编程的过渡过程中的学习曲线陡峭。一种无需完全切换即可开始的实用方法是使用反应式WebClient。除此之外,从小处着手并衡量收益。我们希望,对于广泛的应用程序,这种转变是不必要的。如果不确定要寻找什么好处,请先了解无阻塞I / O的工作原理(例如,单线程Node.js上的并发性)及其影响。
1.1.5 Servers
Tomcat,Jetty,Servlet 3.1+容器以及非Servlet运行时(例如Netty和Undertow)都支持Spring WebFlux。所有服务器都适应于低级通用API,因此可以跨服务器支持更高级别的编程模型。
Spring WebFlux不具有内置支持来启动或停止服务器。但是,从Spring配置和WebFlux基础结构组装应用程序并用几行代码运行它很容易。
Spring Boot具有一个WebFlux启动器,可以自动执行这些步骤。默认情况下,入门者使用Netty,但通过更改Maven或Gradle依赖关系,可以轻松切换到Tomcat,Jetty或Undertow。 Spring Boot默认为Netty,因为它在异步,非阻塞空间中得到更广泛的使用,并允许客户端和服务器共享资源。
Tomcat和Jetty可以与Spring MVC和WebFlux一起使用。但是请记住,它们的使用方式非常不同。 Spring MVC依靠Servlet阻塞I / O,并允许应用程序在需要时直接使用Servlet API。 Spring WebFlux依赖于Servlet 3.1非阻塞I / O,并在低级适配器后面使用Servlet API,并且不公开供直接使用。
对于Undertow,Spring WebFlux直接使用Undertow API,而无需使用Servlet API。
1.1.6 Performance
Performance具有许多特征和意义。 反应和非阻塞通常不会使应用程序运行得更快。 在某些情况下,它们可以(例如,如果使用WebClient并行执行远程调用)。 总体而言,以非阻塞方式进行操作需要更多的工作,这可能会稍微增加所需的处理时间。
反应性和非阻塞性的主要预期好处是能够以较少的固定数量的线程和较少的内存进行扩展。 这使应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。 但是,为了观察这些好处,您需要有一些延迟(包括缓慢的和不可预测的网络I / O)。 这就是反应堆开始显示其优势的地方,差异可能很大。
1.1.7 Concurrency Model
Spring MVC和Spring WebFlux都支持带注解的控制器,但是并发模型以及对阻塞和线程的默认假设存在关键差异。
在Spring MVC(通常是servlet应用程序)中,假定应用程序可以阻塞当前线程(例如,用于远程调用),因此,servlet容器使用大线程池来吸收请求期间的潜在阻塞。 处理。
在Spring WebFlux(通常是非阻塞服务器)中,假定应用程序未阻塞,因此,非阻塞服务器使用固定大小的小型线程池(事件循环工作器)来处理请求。
“按比例缩放”和“少量线程”听起来可能是矛盾的,但是从不阻塞当前线程(而是依靠回调)意味着您不需要额外的线程,因为没有阻塞调用可以吸收。
调用阻止API
如果确实需要使用阻止库怎么办? Reactor和RxJava都提供了publishOn运算符以继续在其他线程上进行处理。这意味着容易逃生。但是请记住,阻塞式API不适用于此并发模型。
可变状态
在Reactor和RxJava中,您可以通过运算符声明逻辑,然后在运行时形成一个反应式管道,在其中以不同的阶段顺序处理数据。这样做的主要好处是,它使应用程序不必保护可变状态,因为该管道中的应用程序代码永远不会被同时调用。
线程模型
您期望在运行Spring WebFlux的服务器上看到哪些线程?
在“原始” Spring WebFlux服务器上(例如,没有数据访问权限或其他可选依赖项),您可以期望该服务器有一个线程,而其他几个线程则可以进行请求处理(通常与CPU核心数量一样多)。但是,Servlet容器可能以更多的线程(例如,Tomcat上为10)开始,以同时支持servlet(阻塞)I / O和Servlet 3.1(非阻塞)I / O使用。
反应式WebClient以事件循环样式运行。因此,您可以看到与之相关的固定数量的处理线程(例如,带有Reactor Netty连接器的react-http-nio-)。但是,如果客户端和服务器都使用Reactor Netty,则默认情况下,两者共享事件循环资源。
Reactor和RxJava提供称为调度程序的线程池抽象,以与publishOn运算符配合使用,该运算符用于将处理切换到其他线程池。调度程序具有建议特定并发策略的名称-例如,“并行”(对于具有有限数量的线程的CPU绑定工作)或“弹性”(对于具有大量线程的I / O绑定)。如果看到这样的线程,则意味着某些代码正在使用特定的线程池调度程序策略。
数据访问库和其他第三方依赖性也可以创建和使用自己的线程。
Configuring
Spring框架不提供启动和停止服务器的支持。要为服务器配置线程模型,您需要使用服务器特定的配置API,或者,如果您使用的是Spring Boot,请检查每个服务器的Spring Boot配置选项。您可以直接配置WebClient。对于所有其他库,请参阅其各自的文档。
最后更新于