1、响应式编程基础
1.1、什么是响应式编程?
响应式编程是一种面向数据流和变化传播的编程范式。
使用它可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。我们可以使用声明的方式构建应用程序的能力,形成更加敏感和有弹性的应用。
1.2、响应式编程的优势
-
提高了代码的可读性,因此开发人员只需要关注定义业务逻辑。
-
在高并发环境中,可以自然的处理消息。
-
可以控制生产者和消费者之间的流量,避免内存不足。
-
对于一个或多个线程,IO绑定任务可以通过异步和非阻塞方式执行,而且不阻塞当前线程。
-
可以有效的管理多个连接系统之间的通信。
1.3、应用场景
-
大量的交易处理服务,如银行部门。
-
大型在线购物应用程序的通知服务,如亚马逊。
-
股票价格同时变动的股票交易业务。
2、Spring 5 响应式编程实践
作为Java中的首个响应式Web框架,Spring 5.0最大的亮点莫过于提供了完整的端到端响应式编程的支持。
2.1 架构介绍
如上图所示左侧是传统的基于Servlet的Spring Web MVC框架,右侧是spring 5.0新引入的基于Reactive Streams的Spring WebFlux框架,从上往下依次是:Router Functions,WebFlux,Reactive Streams三个新组件,其中:
-
Router Functions: 对标@Controller,@RequestMapping等标准的Spring MVC注解,提供一套函数式风格的API,用于创建Router,Handler和Filter。
-
WebFlux: 核心组件,协调上下游各个组件提供响应式编程支持。
-
Reactive Streams: 一种支持背压(Backpressure)的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor。
2.2 简单示例
Spring 5 大力支持了 Reactive Programming(响应式开发),server 和 client 都可以使用这种开发模式,Spring 5 是基于 Reactor项目实现的。
简单来说,Reactive Programming 是一种非阻塞、事件驱动数据流的开发方案,使用函数式编程的概念来操作数据流,系统中某部分的数据变动后会自动更新其他部分,而且成本极低。
reactive streams 是非阻塞的,所以数据的处理过程中无需等待,对于系统的扩展性非常有帮助,因为工作线程不必等待其他资源,可以自由的处理更多的请求。
下面看一个例子,有助于更好的理解,例如我们要从数据库加载用户数据,然后把用户名形成一个新的集合。
① 传统方式
② 函数式处理集合数据流
③ 响应式
函数式比传统方式更加简洁,但如果数据库比较忙,那么我们的线程就被阻塞了,而响应式就可以解决这个问题,非阻塞,主线程不会捆绑在这个操作上,如果调用者也是响应式的,那么就形成了一个非阻塞的传播链条。
如果 web server 是响应式的,那么处理请求的线程就可以立即去处理其他请求,当数据库返回数据后,自动就发送给了调用者。
2.3 背压
使响应式开发超越传统方式的关键因素是背压机制,就是数据流的生产端能够知道消费端的处理能力,并以此调整生产量。
这种感知能力非常重要,比如数据库操作的成本较高,那么在消费端真正准备好处理这些数据之后再进行数据库操作就很关键。
再比如消费端受限(如网络带宽不足),背压机制就可以确保生产端不会过度生产,就是说,当客户端不能很快的消费数据时,就会反向影响到响应式数据流,从而可以尽快通知数据库停止发送数据,数据库也就可以处理其他请求了,使系统的整体效率得到提升。
2.4 Reactor 项目的核心概念
Spring 5 是基于 Reactor 项目实现的响应式开发,Reactor 中有两个核心类型 - Mono 和 Flux。
他们都是数据流,Mono 是一个最多有1个值的流,Flux 是一个可以有无限个值的流。
流的处理是延迟的,生产者只有在收到消费者的指示时才会真正生产数据,是通过调用 subscribe() 来实现的,例如:
subscribe() 中传入的是一个消费者,会处理接收到的每条数据。
Java 9 中已经集成了reactive stream,思路和Reactor项目相同。
2.5 完整示例
控制器
可以看到,和我们之前的写法没有多少不同,还是使用熟悉的注解,只是返回类型不同了,使用了 Flux 和 Mono,用来返回响应式类型的数据,其他的工作都由框架来帮我们做。
我们再简单对比下传统方式和响应式的不同:
传统方式下,数据是全部从数据库读取出来之后才从server发送到client,图示:
响应模式下,只要有数据就绪了,就立即发送给client,像流一样,图示:
这样client就可以更快的展示首条数据,server也不需要存放全部数据,数据的处理和传输都是即时的。
操作数据库的代码也需要是响应式的,Spring Data 已经提供了支持,只是目前还不全面,例如 MongoDB没问题,但 JDBC 还不行,需要等待一段时间。
下面以 mongodb 为例,看看响应式的数据库操作代码什么样:
和我们平时的代码有两点不同,一是使用的接口从普通的 CrudRepository 变为 ReactiveCrudRepository,二是返回类型使用了 Flux,编码方式变动非常小。
3、总结
Reactive Programming 是非常好的开发方法,可维护性和可扩展性都非常好,相对于阻塞式开发,相同资源下性能会得到明显提升。