响应式编程(Reactive Programming)

响应式编程

  • 响应式编程,是一种编程范式(编程风格),更具体的说是一种技术手段,其专注于通过临时数据流链进行计算,通常是事件驱动的,常见的实现方式有2种,一种是回调,将回调函数注册到事件源,特定事件发生时调用回调函数,另一种是声明式的,通过函数组合,例如map,filter

  • 响应式编程的优势在于,它在执行过程中是异步非阻塞的,可以充分利用多核CPU的性能,并且可以由底层的执行模型负责通过数据流来自动传播变化,比如 a+b=c,当a或b改变时,c会自动改变,相比传统的编程范式需要重新计算a+b来得到c,可以做到更实时的响应,使用响应式编程风格开发的程序,能支持更大的吞吐量

    支持更大的吞吐量,不代表程序跑的更快,因为响应式编程只是让执行的主线程不会因为某些耗时操作而阻塞,导致其阻塞后面的请求,但实际执行耗时操作的子线程所消耗的时间依然是没有变化的。可以类比Reactor线程模型通过单个线程接收请求然后转发给selector进行后续处理,接收请求的线程不会阻塞,可以一直接收请求,但是后续selector轮训一遍需要10s,这个时间不会少。

  • 在Java平台,支持响应式编程的流行库有Reactor, RxJava, Vert.x等,而在Java9中,实现了响应式流规范(Reactive Streams)所定义的相关接口,让Java本身支持了响应式编程,不需要通过其他三方库实现

响应式宣言

  • 响应式宣言其实是一组系统架构的原则,更抽象,其中声明了一个响应式系统应该具有的4个特性:即时响应性回弹性弹性消息驱动

  • 即时响应性(Responsive):系统应该尽可能地即时响应客户端的请求

  • 回弹性(Resilient):系统应该在出现问题时依然能保持即时响应性,同时故障应该能被遏制,不会扩散导致整个系统崩溃,并且故障的部分应该做到独立恢复,这就要求系统结构应该是通过组件/模块组合而构建出来的,确保每个组件/模块之间相互隔离,组件的故障恢复可以委托其他外部组件来恢复(也就是说,系统中的组件应该有熔断,不会造成牵一发动全身的灾难性场面,有多副本形成高可用,挂了能够自动恢复,是不是高可用可伸缩架构的要求?)

  • 弹性(Elastic):系统应该能做到根据输入速率的变化做出反应,适当的调整系统所占资源或者系统的负载(说白了就是能够自动缩/扩容,能够做到负载均衡)

  • 消息驱动(Message Driven):使用消息,可以让系统中具体的各功能组件具有松耦合,隔离,位置透明,边界清晰明确的特点,配合背压,可以做到及时让上游服务进行流控,负载管理等

响应式编程与响应式宣言

  • 响应式编程是一种编程范式,一个技术手段,更具体;响应式宣言是提出了一组系统架构的原则,阐述了响应式系统应该具有的特性,更抽象

  • 响应式编程很适合用于开发响应式系统,但不是唯一的选择。

  • 响应式系统需要考虑数据一致性(data consistency)、跨结点沟通(cross-node communication)、协调(coordination)、版本控制(versioning)、编排(orchestration)、错误管理(failure management)、关注与责任分离(concerns and responsibilities)等内容

  • 响应式编程基于事件驱动,响应式系统基于消息驱动,关于事件驱动和消息驱动的差别,响应式宣言官网给出了解释

    一条消息就是一则被送往一个明确目的地的数据。一个事件则是达到某个给定状态的组件发出的一个信号。在一个消息驱动系统中,可寻址到的接收者等待消息的到来然后响应它,否则保持休眠状态。在一个事件驱动系统中,通知的监听者被绑定到消息源上,这样当消息被发出时它就会被调用。这意味着一个事件驱动系统专注于可寻址的事件源而消息驱动系统专注于可寻址的接收者。

响应式编程的缺点

  • 响应式编程风格与传统的命令式编程风格差异大,且响应式编程主要是异步的,相较于同步的编程方式在思维上有所变化,需要一定的学习成本
  • 因为其基于事件驱动,每次触发事件会使用新线程执行,如果在一个不会阻塞的程序中使用,线程切换可能比程序本身执行耗时要多
  • 对于异常的处理,因为基于回调或者声明式的原因,在使用匿名回调时,没有寻址能力,意味着在整个数据处理链中,每个处理节点的处理成功或错误的状态不会向外界发送相应信号,其中某个节点出错异常可能会被吞掉,或者进行了相关处理,但得到的异常信息少,不好定位

响应式编程中的Mono与Flux

  • Mono与Flux,可以看作是2个集合类型,是Reactor中的基本概念,同时也是具体编码过程中的基础API,响应式编程中操作的具体数据,基本都是通过操作这两个对象来进行的,Mono中的元素只能有0-1个(最多一个),Flux中的元素可以有0-n个(无限),Mono与Flux之间可以进行转换,具体转换规则为:
    • 2个Mono对象合并得到一个Flux
    • 一个Flux进行计算操作得到一个Mono
  • 不管是Mono还是Flux,其每次处理都包含3个状态方法,onNext(正常的包含元素的消息),onComplete(序列结束的消息),onError(序列出错的消息)。在处理过程中当消息通知产生时,订阅者中对应的方法 onNext, onComplete和 onError会被调用

Spring WebFlux

  • Spring WebFlux是基于Reactor实现的响应式web框架,内部支持Servlet3.1, Netty,Jetty,Undertow等容器, 默认使用Netty,并且对SpringMVC做了兼容,可以使用@RestController, @RequestMapping等注解开发,也可使用WebFlux独有的RouterFunction, HandlerFunction等API开发

参考文档

Q.E.D.