反应式编程(Reactive Programming)是一套完整的编程体系,既有其指导思想,又有相应的框架和库的支持,并且在生产环境中有大量实际的应用。在支持度方面,既有大公司参与实践,也有强大的开源社区的支持。反应式编程出现的时间并不短,它所受到的关注度不断升高。这主要体现在主流编程平台和框架增强了对它的支持,使它得到了更多的受众,同时也反映了其在开发中的价值。就 Java 平台来说,几个突出的事件包括:Java 9中把反应式流规范以 java.util.concurrent.Flow 类的方式添加到了标准库中;Spring 5 对反应式编程模型提供了内置支持,并增加了新的 WebFlux 模块来支持反应式Web应用的开发。
反应式编程所涵盖的内容很多。与其他编程范式一样,反应式编程要求开发人员改变其固有的思维模式,以不同的角度来看问题。对于熟悉了传统面向对象编程范式的人来说,这样的思想转变可能并不那么容易。反应式编程在解决某些问题时有其先天的优势。在对应用性能要求很高的今天,反应式编程更大有用武之地。作为开发人员来说,根据项目的需求和特征,选择最适合的编程模型可以达到事半功倍的效果。
反应式编程相关的术语目前并没有非常统一的翻译方法,本文中尽量使用较为常见的译法或英文原文。
在讨论反应式编程之前,首先必须要提到的是《反应式宣言(The Reactive Manifesto)》。反应式宣言中对反应式系统(Reactive Systems)的特征进行了定义,有如下四个:
- 及时响应(Responsive):系统在尽可能的情况下及时响应请求。
- 有韧性(Resilient):系统在出现失败时仍然可以及时响应。
- 有弹性(Elastic):在不同的负载下,系统仍然保持及时响应。
- 消息驱动(Message Driven):系统使用异步消息传递来确定不同组件之间的边界,并确保松散耦合、隔离和位置透明性。
这四个特征互相关联和影响。及时响应是核心价值,是反应式系统所追求的目标。有韧性和有弹性是反应式系统的外在表现形式,通过它们才能实现及时响应这个核心价值。消息驱动则是实现手段。
反应式系统的特征
反应式编程的重要概念之一是负压(back-pressure),是系统在负载过大时的重要反馈手段。当一个组件的负载过大时,可能导致该组件崩溃。为了避免组件失败,它应该通过负压来通知其上游组件减少负载。负压可能会一直级联往上传递,最终到达用户处,进而对响应的及时性造成影响。这是在系统整体无法满足过量需求时的自我保护手段,可以保证系统的韧性,不会出现失败的情况。此时系统应该通过增加资源等方式来做出调整。
反应式流
反应式流(Reactive Streams)是一个反应式编程相关的规范。反应式流为带负压的异步非阻塞流处理提供了标准。反应式流规范的出发点是作为不同反应式框架互操作的基础,因此它所提供的接口很简单。
数据传递方式
随着反应式流的出现,我们可以对Java平台上常见的几种数据传递方式做一下总结和比较。
- 直接的方法调用。数据使用者直接调用提供者的方法来获取数据。这种方式是同步的,调用者在方法返回前会被阻塞。调用者和提供者之间的耦合最紧。每次方法调用只能返回一个数据。(虽然可以使用集合类来返回多个数据,但从概念上来说,集合类仍然只能视为一个数据。)
- 使用 Iterable 。 Iterable 表示一个可以被枚举的数据的集合,通常用不同的集合类型来表示,如 List 、 Set 和 Map 等。 Iterable 定义了可以对集合的数据所进行的操作。这些操作是同步的。 Iterable 所包含的数据数量是有限的。
- 使用 Future 。 Future 表示的是一个可以在未来获取的结果,由一个异步操作来负责给出这个结果。在获取到 Future 对象之后,可以使用 get 方法来获取到所需要的结果。虽然计算的过程是异步的, get 方法使用时仍然是阻塞的。 Future 只能表示一个结果。
- 反应式流。反应式流表示的是异步无阻塞的数据流,其中包含的元素数量可能是无限的。
上述四种数据传递方式,实际上代表了两个维度上的四个不同的值,如下表所