响应式编程学习记录
一、核心概念
响应式编程是一种通过异步和数据流来构建事物关系的编程模型。这里每个词都很重要,“事物的关系”是响应式编程的核心理念,“数据流”和“异步”是实现这个核心理念的关键。异步和数据流都是为了正确的构建事物的关系而存在的。只不过,异步是为了区分出无关的事物,而数据流(事件流)是为了联系起有关的事物。
响应式编程至少有如下好处:
-
在业务层面实现代码逻辑分离,方便后期维护和拓展
-
极大提高程序响应速度,充分发掘CPU的能力
-
帮助开发者提高代码的抽象能力和充分理解业务逻辑
-
Rx丰富的操作符会帮助我们极大的简化代码逻辑
二、响应式宣言
不知道是不是为了向敏捷宣言致敬,响应式宣言中也包含了 4 组关键词:
- Responsive: 可响应的。要求系统尽可能做到在任何时候都能及时响应。
- Resilient: 可恢复的。要求系统即使出错了,也能保持可响应性。
- Elastic: 可伸缩的。要求系统在各种负载下都能保持可响应性。
- Message Driven: 消息驱动的。要求系统通过异步消息连接各个组件。
可以看到,对于任何一个响应式系统,首先要保证的就是可响应性,否则就称不上是响应式系统。从这个意义上来说,动不动就蓝屏的 Windows 系统显然不是一个响应式系统。
三、主要特征
除了异步编程,还包含两个重要的关键词:
- Data streams:即数据流,分为静态数据流(比如数组,文件)和动态数据流(比如事件流,日志流)两种。基于数据流模型,RP 得以提供一套统一的 Stream 风格的数据处理接口。和 Java 8 中的 Stream API 相比,RP API 除了支持静态数据流,还支持动态数据流,并且允许复用和同时接入多个订阅者。
- The propagation of change:变化传播,简单来说就是以一个数据流为输入,经过一连串操作转化为另一个数据流,然后分发给各个订阅者的过程。这就有点像函数式编程中的组合函数,将多个函数串联起来,把一组输入数据转化为格式迥异的输出数据。
从本质上说,RP 是一种异步编程框架,和其他框架相比,RP 至少包含了以下三个特性:
- 描述而非执行:在你最终调用 subscribe() 方法之前,从发布端到订阅端,没有任何事会发生。就好比无论多长的水管,只要水龙头不打开,水管里的水就不会流动。为了提高描述能力,RP 提供了比 Stream 丰富的多的多的API,比如 buffer(), merge(), onErrorMap() 等。
- 提高吞吐量: 类似于 HTTP/2 中的连接复用,RP 通过线程复用来提高吞吐量。在传统的Servlet容器中,每来一个请求就会发起一个线程进行处理。受限于机器硬件资源,单台服务器所能支撑的线程数是存在一个上限的,假设为T,那么应用同时能处理的请求数(吞吐量)必然也不会超过T。但对于一个使用 Spring 5 开发的 RP 应用,如果运行在像 Netty 这样的异步容器中,无论有多少个请求,用于处理请求的线程数是相对固定的,因此最大吞吐量就有可能超过T。
- 背压(Backpressure)支持:简单来说,背压就是一种反馈机制。在一般的 Push 模型中,发布者既不知道也不关心订阅者的处理速度,当数据的发布速度超过处理速度时,需要订阅者自己决定是缓存还是丢弃。如果使用 RP,决定权就交回给发布者,订阅者只需要根据自己的处理能力问发布者请求相应数量的数据。你可能会问这不就是 Pull 模型吗?其实是不同的。在 Pull 模型中,订阅者每次处理完数据,都要重新发起一次请求拉取新的数据,而使用背压,订阅者只需要发起一次请求,就能连续不断的重复请求数据。
和任何框架一样,有优势必然就有劣势。RP 的两个比较大的问题是:
- 虽然复用线程有助于提高吞吐量,但一旦在某个回调函数中线程被卡住,那么这个线程上所有的请求都会被阻塞,最严重的情况,整个应用会被拖垮。
- 难以调试。由于 RP 强大的描述能力,在一个典型的 RP 应用中,大部分代码都是以链式表达式的形式出现,比如flux.map(String::toUpperCase).doOnNext(s -> LOG.info("UC String {}", s)).next().subscribe(),一旦出错,你将很难定位到具体是哪个环节出了问题。所幸的是,RP 框架一般都会提供一些工具方法来辅助进行调试。
四、事件驱动与消息驱动(响应式编程与响应式系统基础)
响应式编程是异步编程下的一个子集,也是一种范式,在这种范式下,由新信息的有效性availability推动逻辑的前进,而不是让一条执行线程a thread-of-execution去推动控制流control flow。它能够把问题分解为多个独立的步骤,这些独立的步骤可以以异步且非阻塞non-blocking的方式被执行,最后再组合在一起产生一条工作流workflow——它的输入和输出可能是非绑定的unbounded。
响应式编程一般是事件驱动event-driven,相比之下,响应式系统则是消息驱动message-driven的
在 JVM 中,支持响应式编程的流行库有 Akka Streams、Ratpack、Reactor、RxJava 和 Vert.x 等等。这些库实现了响应式编程的规范,成为 JVM 上响应式编程库之间的互通标准standard for interoperability,并且根据它自身的叙述是“……一个为如何处理非阻塞式反压异步流提供标准的倡议”。
响应式编程——专注于短时间的数据流链条上的计算——因此倾向于事件驱动,而响应式系统——关注于通过分布式系统的通信和协作所得到的弹性和韧性——则是消息驱动的。
一个拥有长期存活的可寻址long-lived addressable组件的消息驱动系统跟一个事件驱动的数据流驱动模型的不同在于,消息具有固定的导向,而事件则没有。消息会有明确的(一个)去向,而事件则只是一段等着被观察observe的信息。另外,消息式messaging更适用于异步,因为消息的发送与接收和发送者和接收者是分离的。
一条消息就是一则被送往一个明确目的地的数据。一个事件则是达到某个给定状态的组件发出的一个信号。在一个消息驱动系统中,可寻址到的接收者等待消息的到来然后响应它,否则保持休眠状态。在一个事件驱动系统中,通知的监听者被绑定到消息源上,这样当消息被发出时它就会被调用。这意味着一个事件驱动系统专注于可寻址的事件源而消息驱动系统专注于可寻址的接收者。
分布式系统需要通过消息在网络上传输进行交流,以实现其沟通基础,与之相反,事件的发出则是本地的。在底层通过发送包裹着事件的消息来搭建跨网络的事件驱动系统的做法很常见。这样能够维持在分布式环境下事件驱动编程模型的相对简易性,并且在某些特殊的和合理的范围内的使用案例上工作得很好。
http://www.jianshu.com/u/1d8042233f67
https://mp.weixin.qq.com/s?src=11×tamp=1513956802&ver=590&signature=egHwIUWcz*Vc3BO8P6tai8AN9zXXJ1dVEC*Nx63OLFb18yFZyuRee7QBNewRb*IohMoF8LVUuYQVIDsokDrKKwYo3ZoeusuvhiWrIMtew*S9kPSxT2tGmGtLi9RDC8Ac&new=1
https://mp.weixin.qq.com/s?src=11×tamp=1513955884&ver=590&signature=o1BXvyNE38arjgzxAk43vt848PkBwFOg-dnCbqzm8Fs7PNSYmZjJSCut4lw*HPLIzXE7-SZvMpzk6Jn9PYJKUVriVqPf4OvEo-BI*y-DdMqVZdQfWbnMUpdMA0thSCFK&new=1