这几年随着微服务编程的兴起,当我们在构建一个应用时通常不会采用传统的单体模式(所谓的单体模式,就是将所有功能包含在一个大而全的工程中),而是用许多只负责某一特定功能的微服务来实现。这些划分为一个个的微服务通常只做一件事情,也通常能够将这个功能做的很好。
但是,当我们采用微服务架构时,首先要考虑的就是如何将所要构建的应用拆分成粒度大小合适、功能相对独立的一个个微服务,而且也需要考虑这些微服务之间如何进行通信(也就是彼此互相调用)。此时,这些微服务之间的交互就变成了重中之重。不过也不用太担心,现在业界已经有了许多成熟的解决方案,有兴趣的同学可以参考我之前一系列的关于Spring Cloud的文章,这里就不扩展说明了。
你可能会纳闷,标题不是说响应式编程,为何一开始就大谈特谈微服务。不用担心哈,接下来的及后续的一系列文章,我们所讲的重点也不是微服务!我们要讲的是在构建微服务时所隐藏的一个架构问题。这个问题就是:一旦我们将应用转换为微服务架构,那么随之而来就是要面对这个应用将变成一个分布式系统,不论你愿不愿意承认,它就在那里。而我们当然知道分布式系统的优点,但也深知它的一个致命缺点:服务雪崩。
或许你之前已经学习过Spring Cloud。Spring Cloud中的Hystrix或Resilience4j不是一个现成的解决方案么?当然它们是解决服务雪崩的一个利器,但是要记住它是站在整体架构上的一个解决方案,而当我们深入到业务实现层面时不免会显得过于粗旷(谁让我们都是苦命的coder)。那么有没有其它更细颗粒的解决方案呢?
先不要着急给答案。还是先让我们再次回到微服务架构所要解决的几个问题,也可以说是目标:
- 各个微服务之间是一种松耦合式的合作关系。这个也是我们编程以来一直追求的目标;
- 快速响应用户的请求。俗话说:天下功夫唯快不破;
- 系统具有良好的弹性,能够应对不同的压力负荷。谁不想构建一个微信级别的应用呢;
- 达到4个9或5个9的可用性。
针对上面的这些问题,有一波业界大神们提出了The Reactive Manifesto: 反应式宣言(熟悉么?有同学记得敏捷宣言么?)。该宣言是一份构建现代云扩展架构的处方,可以妥妥的解决上面问题。在该宣言中给出的处方主要是使用消息驱动的方式来构建应用,在形式上可以达到弹性和韧性,最后可以产生响应性价值。该架构可以使用下图来表示:
该宣言的核心是借助消息驱动来完成异步、松耦合、隔绝、地址透明、将错误作为消息和无阻塞式的编程范型。这种编程范型也就称之为:响应式编程。而将我们现在所经常使用的编程范型则称之为:命令式编程。
命令式编程是一种阻塞模式的编程,也就是代码是按照所编写的顺序一行一行的执行。响应式编程则是一种非阻塞的异步编程,我们无需等待。此外,响应式编程是一种数据流编程模式,程序所关注的是数据流而非控制流。
对反应式宣言感兴趣的同学可以从这里反应式宣言前往。
前面说了那么多废话,下面正式进入主题。
1. Reactive Streams
Java在JDK9之后引入了响应式编程范型:Reactive Streams。Reactive Streams提供了一套非阻塞具有背压的异步流处理标准。主要应用在JVM、JavaScript及网络协议的处理中。我们也可以这么理解: Reactive Streams定义了一套响应式编程的标准。
Reactive Streams定义了下面4个API接口:
Publisher<T>
: 即事件或数据的发生源,它只有一个订阅方法:subscribe()
;Subscriber<T>
: 事件或数据的订阅者,也可以称为事件或数据的消费者。该接口提供了4个方法,分别用于处理Publisher
所发出的不同消息;-
Subscription
: 该接口用来描述每个订阅消息,有点类似我们在进行Web编程时所使用的会话信息(session),只不过该接口用来处理订阅者和发布者之间的关联信息。该接口有2个方法:request()
和cancel()
:-
request()
方法则是向上游消息发布者(Publisher)索要指定个数的消息; -
cancel()
则是告诉消息发布者取消消息的推送。注意,一旦我们调用了该方法,则Subscriber之后不再接受任何Publisher所发布的消息;
-
Processor<T, R>
: 该接口同时继承了Publisher
和Subscriber
这两个接口。它即是一个消息发生者,也是一个消息的订阅者,一般可用对所要发布的消息进行转换、合并等处理,可以理解为是发生者和订阅者之间的一个处理管道。
需要着重说明的一点是:虽然Reactive Streams是Java所提出的一个响应式编程的标准,但是并在JDK中提供任何具体的实现,具体的实现则是由第三方提供,如:RxJava、Reactor、akka...
2. 观察者模式(Observer Pattern)
在我们进入Reactive Streams正题之前,让我们先来回顾一个古老的Java编程模式:观察者模式(Observer Pattern)。观察者模式是GoF(Gang of Four)最早提出的23个著名模式中的一个(如果你还没有听说过,那么赶快去读这本书《设计模式:可复用面向对象软件的基础》)。
我们在开发一个软件应用时,常常会有这样需求:当一个对象的状态发生变化的时候,某些其它的对象要做出相应的响应或改变。当然我们可以使用n多种方法来做到这一点,但是在具体开发时一方面要使所开发出的代码能够易于复用,另外一方面还要尽量选择低耦合度的设计方案。此时观察者模式就是我们最好的选择。
观察者模式是指当对象之间如果存在一对多的依赖关系时,我们可让依赖者们同时监听主题对象。这样当这个主题对象在状态上发生变化时,就会通知所有依赖者,也就是观察者对象,使它们能够自动做出响应。观察者模式属于对象的行为型模式之一。有时我们也将观察者模式称为发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式或从属者(Dependents)模式。
或许,你会非常奇怪,我们不是在讲响应式编程么,这个和观察者模式有什么关系。不要着急,相信我,这个模式和我们所要讲的响应式编程还真的有很大很大的关系。
在一个典型的观察者模式实现中包含了以下两个接口:Subject
和Observer
。一种可能的代码如下:
public interface Subject<T> {
void attach(Observer<T> observer);
void detach(Observer<T> observer);
void notify(T event);
}
public interface Observer<T> {
void update(T event);
}
这里Subject
是主题,也就是会引发变化的对象,而Observer
就是观察者,通常也称之为监听者,监听Subject
所产生的事件并进行响应。
不过Observer
可能负责向Subject
注册,也有可能不负责向Subject
注册。Subject
则会保存所有注册的Observer,
并在状态发生变化时通知给所有的Observer
。而Subject
和Observer
之间并不需要对彼此知道太多,我们可以用下图来表示它们之间的关系:
观察者模式最常用的一个场景就是我们所熟知的MVC(Model-View-Controller)开发模式。此外,做过UI层面开发的也知道,用户操作事件处理也是一个典型的观察者模式应用。
非常幸运,当我们在开发中需要使用观察者模式,不需要从头一点点实现。Spring框架为我们提供了方便的脚手架(工具类):@EventListener
和ApplicationEventPublisher
。
-
@EventListener
用来注解哪些需要监听并处理相应业务的组件或类; -
ApplicationEventPublisher
则是可以让我们发布所定义的各种事件。
不过需要澄清的一点,Spring所提供的事件机制并不完全是一个标准观察者模式的实现,而是在其上的一个变体:发布-订阅模式(Publish-Subscribe)。相应的,在Spring中将Subject
和Observer
这两个概念分别转化为Publisher
和Subscriber
,并进一步通过所提供的EventChannel
进行两者之间的解耦。该模式我们可以用下图来表示:
该模式使得订阅者(Subscriber
)只需要关注EventChannel
,而无需知道一个消息具体是哪一个发布者所发布。同时对于发布者来说,其可以将自己所发布的消息发布到同一个EventChannel
中,也无须关心到底是哪一个订阅者进行了消费。
而这个EventChannel
也就是我们所熟知的消息代理(Message broker),如:kafka、RabbitMQ或者Spring Cloud中的Event Bus。同时借助EventChannel
我们还可以增加一些消息过滤器,使用这些过滤器就可以对消息进行一些通用的处理。因此,通过发布-订阅模式我们可以非常容易借助现有的消息中间件完成各微服务之间的协作,并且解耦。
可见通过观察者模式我们可以实现多个对象之间、多个服务之间关系的解耦,使得它们之间无须知道彼此的具体实现细节,甚至也不需要相互依赖。耦合的减少利于系统的复用,同时观察者模式使得这些低耦合度的对象、服务之间能够维持行动的协调一致,保证高度的协作(Collaboration),为构建分布式应用奠定了基础。可以这么说,响应式编程就是建筑在这种模式之上的更为灵活的异步非阻塞的编程模式。
到这里,我想你应该明白之前为什么说观察者模式(Observer Pattern)和我们接下来所要讲的响应式编程之间的关系了吧(如果还是不明白也不用担心,随着后面的讲解我想你一定会明白的)。
好吧,下一篇让我们正式开启响应式编程之旅。