导言
反应式“reactive”这个词近年来出现频率非常高,但提到响应式时常会令人困惑,它被用来指代许多不同的东西。你可能听说过反应式编程(Reactive Programming)、反应式扩展(Reactive Extensions)、反应式流(Reactive Streams)、反应式消息传递( Reactive Messaging)或反应式系统(Reactive Systems)。但是这些术语的真正含义是什么,它们又是如何组合在一起的呢?
本文将尝试给出其中一些关键术语的定义以及它们之间的关联。
反应式编程(Reactive Programming)
反应式编程是一种通过声明性代码构建异步处理管道的编程范式。换句话说, 代码采用异步数据流方式执行,数据在可用时发送给消费者,这种编程范式编写的程序能够实现快速、异步地对状态变化做出反应。
流(stream)是一系列按时间排序的消息(状态更改)。流中可传递三种消息: 值(value某种数据类型)、错误(error)或完成(complete)信号, 程序中需要定义三种消息对应的处理函数, 消息会异步地被对应的函数捕获处理。对流的监听称作订阅(subscribing)。我们定义的函数是观察者(observable)。流是被观察的主题(或“可观察的对象”)。
响应式编程中,可以为任何内容创建数据流,包括:变量、用户输入、属性、缓存、数据结构等。然后可以订阅这些流并进行相应处理。响应式编程还提供了一个出色的函数工具箱,可以方便的对任意流进行组合、创建和过滤处理,例如:
一个流或多个流可以用作另一个流的输入。
两个流可以被合并。
一个流可以被过滤只保留用户感兴趣的事件的流。
一个流映射的数据将数据值可以被映射到另一个新流。
微服务开发中, 有许多模式和工具能够用于实现反应式编程, 他们包括但不限于:
Futures - 在某个操作完成后保存该操作的结果。
Observables - 是一种软件设计模式,其中称为主体的对象维护其依赖项(观察者)列表,并通过调用它们的方法之一来自动通知它们任何事件(状态更改)。
Publish and Subscribe - 发布和订阅模式
Reactive Streams - 是一种编程概念,用于以非阻塞方式处理异步数据流,同时为流发布者提供回压机制, 下文将详细介绍
Reactive Programming Libraries - 反应式编程库(例如RxJava和SmallRye Mutiny)
响应式编程这种异步和非阻塞执行特性,使其成为在管理微服务内部组件本地逻辑和数据流转换方面非常有用的技术。
反应式扩展(Reactive Extensions)
Reactive Extensions是响应式编程范式的实现。
Reactive Extensions结合了观察者和迭代器模式以及函数式编程,提供了一个包含在程序中创建、组合、合并、过滤和转换数据流功能的工具箱。
有一些流行的 Java 响应式扩展,例如ReactiveX(包括RxJava、RxKotlin、Rx.NET等)和BaconJS。由于有多种库可供选择,而且它们之间缺乏互操作性,因此很难选择使用哪一个。为了解决这个问题,Reactive Streams 标准应运而生。
Reactive Streams
Reactive Streams是一项旨在提供标准以统一反应性扩展并处理具有非阻塞背压的异步流处理的标准,包含运行时环境和网络协议。2015年首次创建的 org.reactivestreams API,包含4个接口:Publisher,Subscriber,Subscription,和Processor。RxJava、Reactor、Akka Streams等扩展框架都实现了这些接口。
Java 开发人员希望标准化 JDK 中的响应式流 API,以便 API 可以自由使用,而无需打包任何第三方库。为了满足这些要求,JDK9在 java.util.concurrent.Flow 中提供了响应式流接口,这在语义上等同于org.reactivestreamsAPI。RxJava、Reactor 和 Akka Streams 都实现了 Flow 下的接口。
Reactive Streams 接口包括:
订阅者和发布者(Subscriber and Publisher)。订阅者是一个流观察者。订阅者通过Publisher.subscribe()方法订阅发布者。然后发布者调用Subscriber.onSubscribe传递订阅,以便订阅者调用subscription.request()(处理回压)或subscription.cancel(), 。
订阅(Subscription)。如果订阅者只能处理 4 个数据项,它将通过Subscription.request(4)传递其处理容量。发布者不会发送超过 4 个,除非订阅者请求更多。Publisher通过调用onNext()发布数据项, 如果没有数据项需要发布则调用onComplete()。
处理器(Processer)。处理器是发布者和订阅者之间的中介。它订阅发布者,然后订阅者订阅处理器。
如上所示,Reactive Streams 引入了publish、subscribe的概念,以及一种将它们联系在一起的方法。但是,通常流还需要map、filter、flatMap 和其他类似于java.util.stream中可用于非反应性流的操作。用户通常并不会自己实现响应式流 API,因为它很复杂,而且很难做到正确并通过 TCKs(允许 Java 技术规范的实现者确定实现是否符合 Reactive Streams 的规范的技术兼容性工具包)。因此,需要引入第三方实现库,例如Akka Streams、RxJava,或 Reactor。
但是,许多 MicroProfile 企业应用程序开发人员不想或不能使用第三方依赖项,但希望能够操作反应式流。因此,为了标准化流操作,MicroProfile Reactive Streams Operators应运而生提供与java.util.stream相同的操作. 下面是一个使用 Reactive Streams Operators 的例子。
反应式消息传递(Reactive Messaging)
Reactive Streams 规范和MicroProfile Reactive Streams Operators规范为 MicroProfile Reactive Messaging 规范提供了基础。
如上所述,Reactive Streams 是一种使用背压进行异步流处理的规范。它定义了一组最小的接口,以允许将执行此类流处理的组件连接在一起。MicroProfile Reactive Streams Operators 是一个 Eclipse MicroProfile 规范,它建立在 Reactive Streams 之上,提供一组基本的操作符来将不同的反应式组件链接在一起,并对在它们之间传递的数据进行处理。
所述MicroProfile反应消息规范允许应用程序组件之间发生异步通信,从而实现微服务的时间解耦。如果无论通信中涉及的组件何时运行、它们是否已加载或过载,以及它们是成功处理消息还是失败,都能够进行通信,则此时间解耦是必要的。它可以在微服务之间实现更大的弹性,这是反应式系统的一个关键特征。
MicroProfile Reactive Messaging 旨在为消息传递提供轻量级的反应式解决方案,以确保使用 MicroProfile 编写的微服务能够满足反应式架构所需的需求,从而提供一种将事件驱动的微服务连接在一起的方法。
它在应用程序的 bean 上使用带注释的方法(@Incoming和@Outgoing),并通过命名通道(一个字符串/名称指示要使用的消息源或目的地)将它们连接在一起。
反应式系统(reactive system)
反应式编程、反应式流和反应式消息传递都是设计和构建反应式系统的有用工具。
反应式系统是为了描述在系统级别提供响应式和反应式应用程序的架构风格。它旨在使由多个微服务组成的应用程序作为一个整体一起工作,以更好地对周围环境和彼此做出反应,在处理不断变化的工作负载需求时表现出更大的回弹性,并在组件出现故障时表现出更大的弹性。
反应式宣言列出了反应式系统的四个关键高级特征:
即时响应性(Responsive):响应式系统需要在合理的时间内处理请求
回弹性(Resilient):反应式系统必须在面对故障(错误、崩溃、超时等)时保持响应,因此它必须被设计为能够优雅地处理故障
弹性(Elastic):反应式系统必须在各种负载下保持响应——能够向上和向下扩展。
消息驱动(Message Driven):来自反应式系统的组件使用异步消息传递进行交互,以实现松散耦合、隔离和位置透明。
反应式系统的核心是异步消息驱动微服务系统。尽管反应式系统的基本原理看似简单,但构建一个反应式系统还是很有挑战的。通常,每个节点都需要采用异步非阻塞范式、任务并发模型并使用非阻塞 I/O。因此,在设计和构建反应式系统时需要将这些要点放在非常重要的位置。然而,使用 反应是编程范式和反应式扩展有助于提供一个开发模型来解决这些挑战。它们可以帮助确保代码保持可读性和可理解性。
一些开源的反应式框架或工具包包括Vert.x、Akka和Project Reactor 等, 这些框架或工具包提供了反应式流规范接口的实现, 可以大大降低我们实施响应式系统的难度。
总结
在本文中,我们试图定义和区分“反应式”可以引用的关键术语,并解释如何将这些规范一起使用以实现非阻塞和反应式系统的设计和构建。