>

背景

许多人都听过异步编程,同步编程,但是好像对响应式编程这个新名称感到很陌生,即使我们在实际项目中已经在使用这种编程方式。

这篇文章就是来介绍响应式编程(Reactive Progamming)到底是怎么回事。

如果我们在 Google 中输入 Reactive Programming,试图了解这到底是怎样的一种编程模式,我们很快会发现大量的文章,但是看完我们就会迷糊,概念还是不是那么清晰。这可能是因为许多介绍文章对领域的强关联导致的。比如最常见的一种介绍是用一种数据流(data stream)的方式来展现,还有就是前端领域的控件事件流,虽然这些场景是响应式编程适合的,但是对我来说,总觉得还是不够直观。因为我面对的是分布式系统的编程场景。

其实响应式编程没必要跟具体的应用领域关联,它是一个可以普遍适用的概念和编程模型。

同步与异步

分布式网络系统中,各个参与方节点的运行是相互独立的,没有共享内存,没有全局时钟。各节点通过消息来进行沟通。在传统的理念中,我们会把这样的网络根据他们通信方式描述成同步和异步的。简单来说,同步网络是对消息的到达时间有限定要求(time bounded),以便保证网络活动的确定性。而异步的网络,则对消息的到达没有任何限制。即使发出的消息丢失了,也不会损害网络的活性。用一个具体的例子来理解就是,节点 A 发送了一个消息给节点 B,期待得到 B 的回复,以便通过某个决议或者完成某件后续事情,这个依赖等待的要求,就是同步网络。因为他依赖一个答复才能进行后续动作。换句话说,如果一个节点发送消息之后,不依赖另一个节点的答复也能正常运行,那么就是异步网络。

在同步网络中,如果 B 由于网络原因(掉线,或者CPU繁忙,等等)没有在限定时间内回复,A 既不能确定消息已经发送给 B,也无法确定后续步骤什么时候才能开始,因此,网络活动变得不可预测,无法结束,也就是没有了确定性。业界已经证明了,在实际的网络中,如果不对网络条件施加任何限制,那么网络确定性是永远无法达到的。因此,为了保证网络活动的确定性,我们通常会网络条件施加一些限制,比如,最典型的就是消息到达的时间限制。

解释了同步网络和异步网络的区别,我们再来看同步编程和异步编程,其实我们接触这两个编程模式很久了。同步编程简单来说就是,发出一个任务,然后等待执行。而异步编程就是,发出一个任务,不等待结果,就继续发出下一个任务。至于上一个任务的执行结果,我们可以通过两种方式获得,一个是主动的轮训,另一个是被动的接收反馈。由于在异步编程中,我们从不等待执行结果,就可以进行其他任务(前提是这个任务本身不依赖上一个任务的结果)。如果要执行的某个其他任务依赖于上一个任务的结果,那么我们可以每隔一段时间轮训一次,或者另外开一个线程去等待接收任务结果。无论哪种情况,我们的网络都不会阻塞在某一个单独的任务上。

响应式编程

现在,我们很自然的过渡到响应式编程(Reactive Programming)这个概念上,它是一种基于事件模式的模型。在上面的异步编程模式中,我们描述了两种获得上一个任务执行结果的方式,一个就是主动轮训,我们把它称为 Proactive 方式。另一个就是被动接收反馈,我们称为 Reactive。

简单来说,在 Reactive 方式中,上一个任务执行结果的反馈就是一个事件,这个事件的到来将会触发下一个任务的执行。

这也就是 Reactive 的内涵!我们把处理和发出事件的主体称为 Reactor,它可以接受事件并处理,也可以在处理完事件后,发出下一个事件给其他 Reactor。两个 Reactors 之间没有必然的强耦合,他们之间通过消息管道来传递消息。Reactor 可以定义一些事件处理函数,根据接收到的事件不同类型来进行不同的处理。如果我们的系统复杂,我们还可以专门定义不同功能类别的 Reactors,分别处理不同类型的事件。而在每个 Reactor 中对事件又进行细分处理。

需要强调的是,实现 Reactive 模型最核心的是线程消息管道。线程用于侦听事件,消息管道用于 Reactor 之间通信不同的消息。与他们相关的是事件管理器用于注册、注销事件,而消息分配器则会根据消息类型分发。

下面是一个 Reactive 模型的示意图:

术语理解

在了解了上述异步编程模型的本质之后,我们再来看一些我们常见的术语,就会发现一切都变得清晰明了了。

比如,依赖链(Dependency chain),假定我们有一个事件依赖链是这样:睡觉 -> 吃饭 -> 饿了,很直觉的是,在这个依赖链中,只有满足了后面的条件,前面的才会执行。

这种依赖链是这个世界普遍的一种场景,一种正向的处理模式是,每隔一段时间就轮训检测是否满足睡觉的条件,在检查是否能睡觉的时候,会先检查是否已经吃饭,检查是否已经吃饭的时候,又会先检查是否饿了。那么这就是 Proactive 模式!而 Reactive 模式则反过来,先有事件的触发,然后事件来到响应方,响应方进行处理,这种方式也叫 Pub/Sub 模式。我们在 OOP 语言中,也会用到同样的概念和逻辑,我们把之叫做 Observer 模式,而在 Funcaitonal Programming 中,也有同样的概念,它可以用 monads 来实现。

比如,我们有一个 Publisher ,会产生 “饿” 事件,同时还有一个或多个 Subscriber,在收到 “饿” 事件的发生之后,进行响应(比如更新状态或者作出其他预先注册好的行为)。

总的来说,Reactive Programming 就是编写关于怎么响应事件的编程模式,这些事件包括:用户输入,数据流,系统状态的变化等等。

总结一下,响应式编程通常会用在一个事件流相关的场景中,在一个事件流中,一旦触发第一个事件,后续的事件会被依次触发,就像一个 Pipeline 系统,不断有输入和输出。

响应式编程的设计与实现

接下来,讲一下实现上的架构设计与实现。

通常 Reactor 的数量可以是预先定义的,因为一个系统,我们通常可以约束它处理哪些预定义的事件,比如有处理网络连接的 Reactor,处理日志收集的 Reactor,处理数据存储的 Reactor 等等,各司其职。而错误(未定义)事件则可以单独放在一个专门处理 Error/Exception 的 Reactor 中。通过事件管理器,每个 Reactor 可以根据要发出或者接收的消息,即时地创建一个线程/协程去执行响应的操作。发出和接收消息可以根据业务的复杂度,分开单独线程,也可以放在一个线程。这样的设计架构简单而清晰。

下面是一个简单的示意图:

全文完!

相关阅读:

P2P 网络核心技术:Gossip 协议
P2P 网络核心技术:UPnP 和 SSDP 协议
P2P 网络核心技术:Kademlia 协议

如果你喜欢我的文章,欢迎关注微信公众号“知辉”,搜索 “deliverit” 或

扫描二维码