为什么创建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端点。
我们谈到了“无障碍”和“功能性”,但是反应式意味着什么?
术语“反应性”是指围绕对更改做出反应的编程模型-网络组件对I / O事件做出反应,UI控制器对鼠标事件做出反应等。从这个意义上说,非阻塞是反应性的,因为随着操作完成或数据可用,我们现在处于响应通知的模式,而不是被阻塞。
我们Spring团队还有另一个重要机制与“反应性”相关联,这是不阻碍背压的机制。在同步命令式代码中,阻塞调用是强制调用者等待的一种自然的背压形式。在非阻塞代码中,控制事件的速率非常重要,这样快速的生产者就不会淹没其目的地。
Reactive Streams是一个小的规范(在Java 9中也采用了),它定义了带有反压力的异步组件之间的交互。例如,数据存储库(充当发布者)可以生成HTTP服务器(充当订阅者)然后可以将其写入响应的数据。 Reactive Streams的主要目的是让订阅者控制发布者生成数据的速度或速度。
常见问题:如果publisher不能放慢脚步怎么办? 反应流的目的仅仅是建立机制和边界。 如果发布者无法放慢速度,则必须决定是缓冲,删除还是失败。
反应流对于互操作性起着重要作用。库和基础结构组件对此很感兴趣,但是由于它太底层了,它作为应用程序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或其他反应式库的使用。有关更多详细信息,请参见反应式库。
spring-web模块包含Spring WebFlux基础的反应式基础,包括HTTP抽象,用于支持的服务器的Reactive Streams适配器,编解码器以及与Servlet API相似但具有非阻塞合同的核心WebHandler API。
在此基础上,Spring WebFlux提供了两种编程模型的选择:
Spring MVC还是WebFlux?
一个自然的问题要问,但建立了一个不合理的二分法。 实际上,两者共同努力扩大了可用选项的范围。 两者的设计旨在实现彼此的连续性和一致性,它们可以并行使用,并且来自双方的反馈对双方都有利。 下图显示了两者之间的关系,它们的共同点以及各自的独特支持:
我们建议您考虑以下几点:
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。
Performance具有许多特征和意义。 反应和非阻塞通常不会使应用程序运行得更快。 在某些情况下,它们可以(例如,如果使用WebClient并行执行远程调用)。 总体而言,以非阻塞方式进行操作需要更多的工作,这可能会稍微增加所需的处理时间。
反应性和非阻塞性的主要预期好处是能够以较少的固定数量的线程和较少的内存进行扩展。 这使应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。 但是,为了观察这些好处,您需要有一些延迟(包括缓慢的和不可预测的网络I / O)。 这就是反应堆开始显示其优势的地方,差异可能很大。
Spring MVC和Spring WebFlux都支持带注解的控制器,但是并发模型以及对阻塞和线程的默认假设存在关键差异。
在Spring MVC(通常是servlet应用程序)中,假定应用程序可以阻塞当前线程(例如,用于远程调用),因此,servlet容器使用大线程池来吸收请求期间的潜在阻塞。 处理。
在Spring WebFlux(通常是非阻塞服务器)中,假定应用程序未阻塞,因此,非阻塞服务器使用固定大小的小型线程池(事件循环工作器)来处理请求。
“按比例缩放”和“少量线程”听起来可能是矛盾的,但是从不阻塞当前线程(而是依靠回调)意味着您不需要额外的线程,因为没有阻塞调用可以吸收。
调用阻止API
如果确实需要使用阻止库怎么办? Reactor和RxJava都提供了publishOn运算符以继续在其他线程上进行处理。这意味着容易逃生。但是请记住,阻塞式API不适用于此并发模型。
可变状态
在Reactor和RxJava中,您可以通过运算符声明逻辑,然后在运行时形成一个反应式管道,在其中以不同的阶段顺序处理数据。这样做的主要好处是,它使应用程序不必保护可变状态,因为该管道中的应用程序代码永远不会被同时调用。
线程模型
您期望在运行Spring WebFlux的服务器上看到哪些线程?
Configuring
Spring框架不提供启动和停止服务器的支持。要为服务器配置线程模型,您需要使用服务器特定的配置API,或者,如果您使用的是Spring Boot,请检查每个服务器的Spring Boot配置选项。您可以直接配置WebClient。对于所有其他库,请参阅其各自的文档。