Spring WebFlux
Spring框架中包含的原始web框架springweb mvc是专门为Servlet API和Servlet容器构建的。反应式堆栈web框架SpringWebFlux是在5.0版本中添加的。它是完全无阻塞的,支持反应流背压,并在Netty、Undertow和servlet3.1+容器等服务器上运行。
两个web框架都反映了它们的源模块(和springwebflux)的名称,并且在spring框架中并排共存。每个模块都是可选的。应用程序可以使用一个或另一个模块,在某些情况下,可以同时使用这两个模块(例如,Spring MVC控制器和反应式WebClient)。
为什么会创建SpringWebFlux?
部分原因是需要一个无阻塞的web堆栈来处理具有少量线程的并发性,并使用较少的硬件资源进行扩展。Servlet3.1确实为非阻塞I/O提供了一个API。但是,使用它会导致ServletAPI的其他部分出现问题,在这些API中契约是同步的(Filter
,Servlet
)或阻塞的(getParameter
,getPart
)。这是一个新的公共API在任何非阻塞运行时作为基础的动机。这一点很重要,因为服务器(如Netty)在异步、非阻塞空间中已经建立良好。
答案的另一部分是函数式编程。正如在Java5中添加注释创造了机会(如带注释的REST控制器或单元测试),在Java8中添加lambda
表达式也为Java中的函数API创造了机会。这对于允许异步逻辑的声明性组合的非阻塞应用程序和延续式api(CompletableFuture
和ReactiveX
对此进行了推广)是一个福音。在编程模型级别,Java8支持SpringWebFlux提供功能性的web端点和带注释的控制器。
定义“反应性”
我们谈到了“无阻塞”和“功能性”,但反应性是什么意思?
术语“reactive
”指的是围绕响应更改而构建的编程模型 - 响应I/O事件的网络组件、响应鼠标事件的UI控制器等。从这个意义上说,非阻塞是被动的,因为我们现在不是被阻塞,而是在操作完成或数据可用时对通知做出反应。
还有另一个重要的机制,我们在Spring团队中与“反应性”联系在一起,那就是无阻塞背压。在同步的命令式代码中,阻塞调用作为一种自然的背压形式,迫使调用方等待。在非阻塞代码中,控制事件的速率变得非常重要,这样快速的生产者就不会压倒其目的地。
Reactive Streams是一个小规范(java9中也采用了这个规范),它定义了异步组件与背压之间的交互。例如,数据存储库(充当发布者)可以生成HTTP服务器(充当订阅者)可以写入响应的数据。反应流的主要目的是让订阅者控制发布者生成数据的速度。
反应性API
反应流对于互操作性起着重要的作用。它对库和基础设施组件很感兴趣,但作为应用程序API用处不大,因为它的级别太低。应用程序需要一个更高级别、更丰富、功能更强大的API来组成异步逻辑 - 类似于Java8 Flow API,但不仅限于集合。这就是反应库所扮演的角色。
Reactor是Spring-WebFlux的首选反应库。它提供了Mono
和Flux API
类型,通过与ReactiveX操作符词汇表对齐的一组丰富的操作符来处理0..1(Mono)
和0..N(Flux)
的数据序列。Reactor是一个反应流库,因此,所有操作人员都支持无阻塞背压。Reactor非常关注服务器端Java。它是与Spring密切合作开发的。
WebFlux需要Reactor作为核心依赖项,但它可以通过反应流与其他反应库进行互操作。作为一般规则,webfluxapi接受普通发布服务器作为输入,在内部将其调整为Reactor类型,使用该类型,并返回Flux
或Mono
作为输出。因此,您可以将任何发布者作为输入传递,并且可以对输出应用操作,但是您需要调整输出以与另一个反应库一起使用。只要可行(例如,带注释的控制器),WebFlux就会透明地适应RxJava或其他反应库的使用。
除了反应式api之外,WebFlux还可以与Kotlin中的coroutines api一起使用,后者提供了一种更为必要的编程风格。
编程模型
Spring Web模块包含了Spring WebFrand的基础,包括HTTP抽象、支持服务器的反应流适配器、编解码器和与servlet API相媲美的核心WebAdver-API,但是具有非阻塞契约。
在此基础上,Spring WebFLASH提供了两种编程模型的选择:
带注释的控制器:与springmvc一致,并且基于来自springweb模块的相同注释。springmvc和WebFlux控制器都支持反应式(Reactor
和RxJava
)返回类型,因此很难区分它们。一个显著的区别是WebFlux还支持被动@RequestBody
参数。
功能端点:基于Lambda的轻量级功能编程模型。您可以将其视为一个小型库或一组应用程序可以用来路由和处理请求的实用程序。带注释控制器的最大区别在于,应用程序从头到尾负责请求处理,而不是通过注释声明意图并被回调。
Spring MVC还是WebFlux?
一个自然的问题,但建立一个不健全的二分法。事实上,两者共同努力扩大了可用选项的范围。这两者的设计是为了彼此的连续性和一致性,它们可以并排使用,而且来自每一方的反馈对双方都有利。下图显示了两者之间的关系、共同点以及各自独特的支持:
我们建议您考虑以下几点:
1. 如果您有一个运行良好的springmvc应用程序,则无需进行更改。命令式编程是编写、理解和调试代码的最简单方法。您可以选择最多的库,因为从历史上看,大多数库都是阻塞的。
2. 如果您已经在调研非阻塞的web堆栈,那么SpringWebFlux提供了与此领域其他产品相同的执行模型优势,并且还提供了服务器(Netty、Tomcat、Jetty、Undertow和Servlet3.1+容器)的选择、编程模型的选择(带注释的控制器和功能性web端点)和反应式web端点的选择库(Reactor、RxJava或其他)。
3. 如果您对用于java8 lambdas
或Kotlin的轻量级、功能性web框架感兴趣,可以使用springwebflux功能性web端点。对于需求不太复杂的小型应用程序或微服务来说,这也是一个不错的选择,因为它们可以从更高的透明度和控制中获益。
4. 在微服务体系结构中,可以混合使用带有springmvc或springwebflux控制器或springwebflux功能端点的应用程序。在这两个框架中支持相同的基于注释的编程模型,可以更容易地重用知识,同时为正确的工作选择正确的工具。
5. 评估应用程序的一种简单方法是检查其依赖关系。如果您要使用阻塞持久性api(JPA、JDBC)或网络api,那么springmvc至少是通用体系结构的最佳选择。在Reactor和RxJava中,在单独的线程上执行阻塞调用在技术上是可行的,但是您不会充分利用非阻塞web堆栈。
6. 如果您的springmvc应用程序具有对远程服务的调用,请尝试响应式WebClient。您可以直接从springmvc控制器方法返回反应类型(Reactor、RxJava或其他)。每次调用的延迟或调用之间的相互依赖性越大,好处就越显著。springmvc控制器也可以调用其他反应组件。
7. 如果您有一个庞大的团队,请记住,在向非阻塞、函数式和声明式编程的转变过程中,学习曲线非常陡峭。在没有完全切换的情况下启动的一种实用方法是使用反应式WebClient。除此之外,从小事做起,衡量效益。我们预计,对于广泛的应用,这种转变是不必要的。如果您不确定要寻找什么好处,请从了解非阻塞I/O的工作原理(例如,单线程上的并发在Node.js)以及它的影响。
服务器
SpringWebFlux在Tomcat、Jetty、Servlet3.1+容器以及非ServletRuntime(如Netty和Undertow)上受支持。所有服务器都适用于低级别的通用API,以便跨服务器支持更高级别的编程模型。
springwebflux不支持启动或停止服务器。然而,从Spring配置和WebFlux基础设施组装应用程序并用几行代码运行它是很容易的。
SpringBoot有一个WebFlux启动程序,可以自动执行这些步骤。默认情况下,初学者使用Netty,但是通过改变Maven或Gradle依赖关系,很容易切换到Tomcat、Jetty或Undertow。springboot默认为Netty,因为它更广泛地应用于异步、无阻塞空间,并允许客户机和服务器共享资源。
Tomcat和Jetty可以与springmvc和WebFlux一起使用。但是请记住,它们的使用方式是非常不同的。springmvc依赖于Servlet阻塞I/O,并允许应用程序在需要时直接使用Servlet API。SpringWebFlux依赖于Servlet3.1非阻塞I/O,并在底层适配器后面使用ServletAPI。不暴露直接使用。
对于Undertow,springwebflux直接使用undertowapi,而不使用Servlet API。
Spring webflux性能
性能有许多特点和意义。被动和非阻塞通常不会使应用程序运行得更快。在某些情况下,它们可以(例如,如果使用WebClient并行运行远程调用)。总的来说,它需要更多的工作来完成非阻塞方式的事情,这可以稍微增加所需的处理时间。
反应式和非阻塞的主要预期好处是能够以较小的、固定数量的线程和较少的内存进行扩展。这使得应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。但是,为了观察这些好处,您需要有一些延迟(包括缓慢和不可预测的网络I/O的混合)。这就是反应性堆栈开始显示其优势的地方,而差异可能是巨大的。
并发模型
springmvc和springwebflux都支持带注释的控制器,但是在并发模型和阻塞和线程的默认假设上有一个关键的区别。
在springmvc(以及一般的servlet应用程序)中,假设应用程序可以阻塞当前线程(例如,用于远程调用)。因此,servlet容器使用一个大的线程池来吸收请求处理过程中的潜在阻塞。
在springwebflux(以及一般的非阻塞服务器)中,假设应用程序不阻塞。因此,非阻塞服务器使用一个小的、固定大小的线程池(事件循环工作线程)来处理请求。
“To scale
”和“small number of threads
”听起来可能相互矛盾,但是从不阻塞当前线程(而是依赖回调)意味着您不需要额外的线程,因为没有阻塞调用可以吸收。
调用阻塞API
如果您确实需要使用阻塞库怎么办?Reactor和RxJava都提供publishOn
操作符,以便在不同的线程上继续处理。这意味着有一个容易逃生的舱口。但是,请记住,阻塞api并不适合这种并发模型。
可变状态
在Reactor和RxJava中,通过操作符声明逻辑。在运行时,会形成一个反应管道,在该管道中,数据按不同的阶段依次进行处理。这样做的一个关键好处是,它使应用程序不必保护可变状态,因为管道中的应用程序代码永远不会被并发调用。
线程模型
在运行springwebflux的服务器上,您希望看到哪些线程?
- 在“
vanilla
”springwebflux服务器上(例如,没有数据访问或其他可选依赖项),您可以期望服务器有一个线程,而其他几个线程用于请求处理(通常与CPU内核的数量相同)。但是,Servlet容器可以从更多线程开始(例如,Tomcat上有10个线程),以支持Servlet(阻塞)I/O和Servlet 3.1(非阻塞)I/O使用。 - 反应式WebClient以事件循环方式运行。因此,您可以看到与此相关的少量、固定数量的处理线程(例如,reactor httpnio—带有reactor Netty连接器)。但是,如果Reactor Netty同时用于客户端和服务器,则默认情况下,这两个服务器共享事件循环资源。
- Reactor和RxJava提供了线程池抽象(称为调度程序),与
publishOn
操作符一起使用,后者用于将处理切换到不同的线程池。调度器的名称表明了一种特定的并发策略如,“parallel
”(用于有限线程数的CPU绑定工作)或“elastic
”(用于大量线程数的I/O绑定工作)。如果您看到这样的线程,则意味着某些代码正在使用特定的线程池调度程序策略。 - 数据访问库和其他第三方依赖项也可以创建和使用自己的线程。
配置
Spring框架不支持启动和停止服务器。要为服务器配置线程模型,需要使用特定于服务器的配置api,或者,如果使用Spring Boot,请检查每个服务器的Spring Boot配置选项。您可以直接配置WebClient。对于所有其他库,请参见它们各自的文档。
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/1783.html
暂无评论