写点什么

初入响应式编程 (上)

用户头像
CD826
关注
发布于: 2020 年 04 月 22 日
初入响应式编程(上)

这几年随着微服务编程的兴起,当我们在构建一个应用时通常不会采用传统的单体模式(所谓的单体模式,就是将所有功能包含在一个大而全的工程中),而是用许多只负责某一特定功能的微服务来实现。这些划分为一个个的微服务通常只做一件事情,也通常能够将这个功能做的很好。


但是,当我们采用微服务架构时,首先要考虑的就是如何将所要构建的应用拆分成粒度大小合适、功能相对独立的一个个微服务,而且也需要考虑这些微服务之间如何进行通信(也就是彼此互相调用)。此时,这些微服务之间的交互就变成了重中之重。不过也不用太担心,现在业界已经有了许多成熟的解决方案,有兴趣的同学可以参考我之前一系列的关于 Spring Cloud 的文章,这里就不扩展说明了。


你可能会纳闷,标题不是说响应式编程,为何一开始就大谈特谈微服务。不用担心哈,接下来的及后续的一系列文章,我们所讲的重点也不是微服务!我们要讲的是在构建微服务时所隐藏的一个架构问题。这个问题就是:一旦我们将应用转换为微服务架构,那么随之而来就是要面对这个应用将变成一个分布式系统,不论你愿不愿意承认,它就在那里。而我们当然知道分布式系统的优点,但也深知它的一个致命缺点:服务雪崩。


或许你之前已经学习过 Spring Cloud。Spring Cloud 中的 Hystrix 或 Resilience4j 不是一个现成的解决方案么?当然它们是解决服务雪崩的一个利器,但是要记住它是站在整体架构上的一个解决方案,而当我们深入到业务实现层面时不免会显得过于粗旷(谁让我们都是苦命的 coder)。那么有没有其它更细颗粒的解决方案呢?


先不要着急给答案。还是先让我们再次回到微服务架构所要解决的几个问题,也可以说是目标:


1. 各个微服务之间是一种松耦合式的合作关系。这个也是我们编程以来一直追求的目标;

2. 快速响应用户的请求。俗话说:天下功夫唯快不破;

3. 系统具有良好的弹性,能够应对不同的压力负荷。谁不想构建一个微信级别的应用呢;

4. 达到 4 个 9 或 5 个 9 的可用性。


针对上面的这些问题,有一波业界大神们提出了 The Reactive Manifesto: 反应式宣言(熟悉么?有同学记得敏捷宣言么?)。该宣言是一份构建现代云扩展架构的处方,可以妥妥的解决上面问题。在该宣言中给出的处方主要是使用消息驱动的方式来构建应用,在形式上可以达到弹性和韧性,最后可以产生响应性价值。该架构可以使用下图来表示:


反应式编程的架构图


该宣言的核心是借助消息驱动来完成异步、松耦合、隔绝、地址透明、将错误作为消息和无阻塞式的编程范型。这种编程范型也就称之为:响应式编程。而将我们现在所经常使用的编程范型则称之为:命令式编程


命令式编程是一种阻塞模式的编程,也就是代码是按照所编写的顺序一行一行的执行。响应式编程则是一种非阻塞的异步编程,我们无需等待。此外,响应式编程是一种数据流编程模式,程序所关注的是数据流而非控制流。


对反应式宣言感兴趣的同学可以从这里反应式宣言前往。


前面说了那么多废话,下面正式进入主题。

1. Reactive Streams


Java 在 JDK9 之后引入了响应式编程范型:Reactive Streams。Reactive Streams 提供了一套非阻塞具有背压的异步流处理标准。主要应用在 JVM、JavaScript 及网络协议的处理中。我们也可以这么理解: Reactive Streams 定义了一套响应式编程的标准。


Reactive Streams 定义了下面 4 个 API 接口:

  1. Publisher<T>: 即事件或数据的发生源,它只有一个订阅方法: subscribe();

  2. Subscriber<T>: 事件或数据的订阅者,也可以称为事件或数据的消费者。该接口提供了 4 个方法,分别用于处理Publisher所发出的不同消息;

  3. Subscription: 该接口用来描述每个订阅消息,有点类似我们在进行 Web 编程时所使用的会话信息(session),只不过该接口用来处理订阅者和发布者之间的关联信息。该接口有 2 个方法:request()cancel()

  • request()方法则是向上游消息发布者(Publisher)索要指定个数的消息;

  • cancel()则是告诉消息发布者取消消息的推送。注意,一旦我们调用了该方法,则 Subscriber 之后不再接受任何 Publisher 所发布的消息;

  1. 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)模式。


或许,你会非常奇怪,我们不是在讲响应式编程么,这个和观察者模式有什么关系。不要着急,相信我,这个模式和我们所要讲的响应式编程还真的有很大很大的关系。


在一个典型的观察者模式实现中包含了以下两个接口:SubjectObserver。一种可能的代码如下:

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。而SubjectObserver之间并不需要对彼此知道太多,我们可以用下图来表示它们之间的关系:


观察者模式类图


观察者模式最常用的一个场景就是我们所熟知的 MVC(Model-View-Controller)开发模式。此外,做过 UI 层面开发的也知道,用户操作事件处理也是一个典型的观察者模式应用。


非常幸运,当我们在开发中需要使用观察者模式,不需要从头一点点实现。Spring 框架为我们提供了方便的脚手架(工具类):@EventListenerApplicationEventPublisher

  • @EventListener用来注解哪些需要监听并处理相应业务的组件或类;

  • ApplicationEventPublisher则是可以让我们发布所定义的各种事件。


不过需要澄清的一点,Spring 所提供的事件机制并不完全是一个标准观察者模式的实现,而是在其上的一个变体:发布-订阅模式(Publish-Subscribe)。相应的,在 Spring 中将SubjectObserver这两个概念分别转化为PublisherSubscriber,并进一步通过所提供的EventChannel进行两者之间的解耦。该模式我们可以用下图来表示:

Spring事件机制


该模式使得订阅者(Subscriber)只需要关注EventChannel,而无需知道一个消息具体是哪一个发布者所发布。同时对于发布者来说,其可以将自己所发布的消息发布到同一个EventChannel中,也无须关心到底是哪一个订阅者进行了消费。


而这个EventChannel也就是我们所熟知的消息代理(Message broker),如:kafka、RabbitMQ 或者 Spring Cloud 中的 Event Bus。同时借助EventChannel我们还可以增加一些消息过滤器,使用这些过滤器就可以对消息进行一些通用的处理。因此,通过发布-订阅模式我们可以非常容易借助现有的消息中间件完成各微服务之间的协作,并且解耦。


可见通过观察者模式我们可以实现多个对象之间、多个服务之间关系的解耦,使得它们之间无须知道彼此的具体实现细节,甚至也不需要相互依赖。耦合的减少利于系统的复用,同时观察者模式使得这些低耦合度的对象、服务之间能够维持行动的协调一致,保证高度的协作(Collaboration),为构建分布式应用奠定了基础。可以这么说,响应式编程就是建筑在这种模式之上的更为灵活的异步非阻塞的编程模式。


到这里,我想你应该明白之前为什么说观察者模式(Observer Pattern)和我们接下来所要讲的响应式编程之间的关系了吧(如果还是不明白也不用担心,随着后面的讲解我想你一定会明白的)。


好吧,下一篇让我们正式开启响应式编程之旅。

发布于: 2020 年 04 月 22 日阅读数: 220
用户头像

CD826

关注

Talk is cheap, show me the code. 2020.04.10 加入

还未添加个人简介

评论

发布
暂无评论
初入响应式编程(上)