码农手记 | Spring WebFlux实战以及原理浅析
栏目📰:码农手记 💻
撰文✍🏻:极链科技视联网技术中心张喆闻
编辑📚:果果
关键词📌: WebFlux 、 Spring 5 、 WebMVC 、 反应式编程
将会邀请
Spring 5是流行的spring框架的下一个重大的版本升级。spring5 中最重要改动是把反应式编程的思想应用到了框架的各个方面,spring 5的反应式编程以Reactor库为基础。
Spring 5中WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。该模块中包含了对反应式 HTTP、服务器推送事件和 WebSocket的客户端和服务器端的支持。对于开发人员来说,比较重要的是服务器端的开发。
在服务器端,WebFlux 支持两种不同的编程模型:第一种是 Spring MVC 中使用的基于 Java 注解的方式;第二种是基于 Java 8 的 lambda 表达式的函数式编程模型。这两种编程模型只是在代码编写方式上存在不同。它们运行在同样的反应式底层架构之上,因此在运行时是相同的。
WebFlux 需要底层提供运行时的支持,WebFlux 可以运行在支持 Servlet 3.1 非阻塞 IO API 的 Servlet 容器上,或是其他异步运行时环境,如 Netty 和 Undertow。
反应式编程(Reactive Programming)这种新的编程范式越来越受到开发人员的欢迎。
# 反应式编程介绍
反应式编程来源于数据流和变化的传播,意味着由底层的执行模型负责通过数据流来自动传播变化。比如求值一个简单的表达式c=a+b,当a或者b的值发生变化时,传统的编程范式需要对a+b进行重新计算来得到c的值。
如果使用反应式编程,当a或者b的值发生变化时,c的值会自动更新。
在经历了一些反应式编程实践的基础上产生了后来的反应式流(Reactive Streams)规范。该规范定义了反应式流的相关接口,并集成到了java 9中。
在传统的编程范式中,我们一般通过迭代器(Iterator)模式来遍历一个序列。这种遍历方式是由调用者来控制节奏的,采用的是拉的方式。每次由调用者通过next()方法来获取序列中的下一个值。使用反应式流时采用的则是推的方式,即常见的发布者-订阅者模式。当发布者有新的数据产生时,这些数据会被推送到订阅者来进行处理。在反应式流上可以添加各种不同的操作来对数据进行处理,形成数据链。这个以声明式的方式添加的处理链只在订阅者进行订阅操作时才会真正执行。
反应式流中第一个重要概念就是负压(backpressure)。在基本的消息推送模式中,当消息发布者产生数据的速度过快时,会使得消息订阅者的处理速度无法跟上产生的速度,从而给订阅者造成很大的压力。当压力过大时,有可能造成订阅者本身的奔溃,所产生的级联效应甚至可能造成整个系统的瘫痪。负压的作用在于提供一种从订阅者到生产者的反馈渠道。订阅者可以通过request()方法来声明其一次所能处理的消息数量,而生产者就只会产生相应数量的消息,直到下一次request()方法调用。这实际上变成了推拉结合的模式。
# Reactor 简介
Reactor完全基于反应式流规范设计和实现的库。Reactor也是Spring 5中反应式编程的基础。学习和掌握Reactor可以更好低理解Spring 5中的相关概念。
在Java程序中使用Reactor库非常的简单,只需要通过Maven或Gradle来添加io.projectreactor:reactor-core的依赖即可。
# Flux 和 Mono
Flux和Mono是Reactor中的两个基本概念。Flux表示的是包含0到N个元素的异步序列。在该序列中可以包含三种不同类型的消息通知:正常的包含元素的消息、序列结束的消息和序列出错的消息。当消息通知产生时,订阅者中对应的方法onNext(),onComplete()和onError()会被调用。Mono表示的是包含0或者1个元素的异步序列。该序列中同样可以包含于flux相同的三种类型的消息通知。Flux和Mono之间可以进行转换。对一个Flux序列进行计数操作,得到的结果是一个Mono<Long>对象。把两个Mono序列合并到一起,得到的是一个Flux对象。
# 反应式编程小结
反应式编程范式为开发高性能web应用带来了新的机会和挑战。对于复杂的应用来说,反应式编程和负压的优势会体现出来,可以带来整体的性能的提升。spring5的webflux模块可以作为开发反应式web应用的基础。由于spring框架的流行,webflux会成为开发web应用的重要趋势之一。
webflux内部使用的是响应式编程(Reactive Programming),以Reactor库为基础,基于异步和事件驱动,可以让我们在不扩充硬件资源的前提下,提升系统的吞吐量和伸缩性。
但是,webflux并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。
1️⃣从Dao到Controller,全部都要是Mono和Flux。
2️⃣目前官方的数据层Reactive框架只支持Redis,Mongo等几个,没有JDBC,webflux整个一套都需要是非阻塞的。
3️⃣编程难度高,debug困难。
1️⃣特别适合应用在IO密集型服务中,比如写文件的日志收集、微服务网关这样的应用中。(IO密集型包括:磁盘IO密集型,网络IO密集型,使用异步非阻塞式编程模型,能够显著地提升吞吐量。并且 官方的推荐使用Netty跑WebFlux)
3️⃣如果你的代码中有任何阻塞操作,请谨慎选择WebFlux。
4️⃣WebFlux并不保证应用能运行的更快,但是它主打的是横向扩展和低内存消耗,它的性能需要在一些特定的场景才能展现,比如慢网络IO的场景。
# WebMVC v.s. WebFlux
首先需要指出webflux不是spring mvc的替代方案!webflux强调的是异步非阻塞,spring mvc强调的是同步阻塞,如果方案大部分偏向于非同步,则spring webflux才是首选。另外,如果依赖了大量阻塞式持久化 API 和网络 API,建议使⽤ Spring MVC。
1️⃣已经使⽤了非阻塞技术栈,可以考虑使⽤WebFlux。
2️⃣想要使⽤ Java 8 Lambda 结合轻量级函数式框架,可以考虑 WebFlux。
WebMVC:springMvc构建在servlet api之上,使用的是同步阻塞式i/o模型,每一个请求对应一个线程去处理。
WebFlux:spring webflux是一个异步非阻塞式的web框架,它能够充分利用多核cpu的硬件资源去处理大量的并发请求。
相同点:
1️⃣都可以使用springmvc注解,如@Controller,方便我们在两个web框架中自由转换;
2️⃣都可以使用tomcat,jetty,undertow servlet容器(servlet 3.1+)
# WebFlux相对于SpringMVC下的优点
1️⃣同样的性能场景消耗的资源更少
2️⃣适合横向扩展
注意点:
1️⃣spring mvc因为是使用的同步阻塞式,更方便开发人员编写功能,debug测试等,一般来说,如果spring mvc能够满足的场景,就尽量不要用webflux;
2️⃣webflux默认情况下使用netty作为服务器;
3️⃣webflux目前不支持mysql;
4️⃣WebFlux需要 非阻塞的业务代码,如果阻塞,需要自己开线程池去运行;
# WebFlux什么场景下可以替换SpringMVC呢?
1️⃣想要内存和线程数较少的场景 - 节省机器资源,省钱
2️⃣网络较慢或者IO会经常出现问题的场景
使用过 Spring MVC 的同学们,应该到知道 Spring MVC 的前端控制器是 DispatcherServlet, 而 WebFlux 是 DispatcherHandler,它实现了 WebHandler 接口:
DispatcherHandler类中处理请求的 handle 方法:
1️⃣ServerWebExchange 对象中放置每一次 HTTP 请求响应信息,包括参数等;
2️⃣判断整个接口映射 mappings 集合是否为空,空则创建一个 Not Found 的错误;
4️⃣调用具体业务方法,也就是我们定义的接口方法;
5️⃣处理返回的结果;
详细原理,如果大家感兴趣,可以下次接着分享~
🚀
👇🏻点击「阅读原文」查看更多深度技术好文