微服务之间的最佳调用方式( 二 )


当用RPC方式时,由“Order”服务调用其他几个服务来完成整个功能 。用事件通知方式时,“Checkout”服务完成之后发送“Order Placed”消息,“Payment”服务收到消息,接收用户付款,发送“Payment received”消息 。
“Inventory”服务收到消息,从仓库里取货,并发送“Goods fetched”消息 。“Shipment”服务得到消息,发送货物,并发送“Goods shipped”消息 。

微服务之间的最佳调用方式

文章插图
 
对这个例子来讲,使用事件驱动是一个不错的选择,因为每个服务发消息之后它不需要任何反馈,这个消息由下一个模块接收来完成下一步动作,时间上的要求也比上一个要宽松 。用事件驱动的好处是降低了耦合度,坏处是你现在不能在程序里找到整个购物过程的步骤 。
如果一个业务逻辑有它自己相对固定的流程和步骤,那么使用RPC或业务流程管理(BPM)能够更方便地管理这些流程 。在这种情况下选哪种方案呢?在我看来好处和坏处是大致相当的 。从技术上来讲要选事件驱动,从业务上来讲要选RPC 。不过现在越来越多的人采用事件通知作为微服务的集成方式,它似乎已经成了微服务之间的标椎调用方式 。
事件溯源(Event Sourcing)这是一种具有颠覆性质的的设计,它把系统中所有的数据都以事件(Event)的方式记录下来,它的持久存储叫Event Store,一般是建立在数据库或消息队列(例如Kafka)基础之上,并提供了对事件进行操作的接口,例如事件的读写和查询 。事件溯源是由领域驱动设计(Domain-Driven Design)提出来的 。
DDD中有一个很重要的概念,有界上下文(Bounded Context),可以用有界上下文来划分微服务,每个有界上下文都可以是一个微服务 。下面是有界上下文的示例 。下图中有两个服务“Sales”和“Support” 。
有界上下文的一个关键是如何处理共享成员,在图中是“Customer”和“Product” 。在不同的有界上下文中,共享成员的含义、用法以及他们的对象属性都会有些不同,DDD建议这些共享成员在各自的有界上下文中都分别建自己的类(包括数据库表),而不是共享 。可以通过数据同步的手段来保持数据的一致性 。下面还会详细讲解 。
微服务之间的最佳调用方式

文章插图
【微服务之间的最佳调用方式】 
事件溯源是微服务的一种存储方式,它是微服务的内部实现细节 。因此你可以决定哪些微服务采用事件溯源方式,哪些不采用,而不必所有的服务都变成事件溯源的 。通常整个应用程序只有一个Event Store,不同的微服务都通过向Event Store发送和接受消息而互相通信 。
Event Store内部可以分成不同的stream(相当于消息队列中的Topic),供不同的微服务中的领域实体(Domain Entity)使用 。
事件溯源的一个短板是数据查询,它有两种方式来解决 。第一种是直接对stream进行查询,这只适合stream比较小并且查询比较简单的情况 。
查询复杂的话,就要采用第二种方式,那就是建立一个只读数据库,把需要的数据放在库中进行查询 。数据库中的数据通过监听Event Store中相关的事件来更新 。
数据库存储方式只能保存当前状态,而事件溯源则存储了所有的历史状态,因而能根据需要回放到历史上任何一点的状态,具有很大优势 。但它也不是一点问题都没有 。
第一,它的程序比较复杂,因为事件是一等公民,你必须把业务逻辑按照事件的方式整理出来,然后用事件来驱动程序 。第二,如果你要想修改事件或事件的格式就比较麻烦,因为旧的事件已经存储在Event Store里了(事件就像日志,是只读的),没有办法再改 。
由于事件溯源和事件通知表面上看起来很像,不少人都搞不清楚它们的区别 。事件通知只是微服务的集成方式,程序内部是不使用事件溯源的,内部实现仍然是传统的数据库方式 。
只有当要与其他微服务集成时才会发消息 。而在事件溯源中,事件是一等公民,可以不要数据库,全部数据都是按照事件的方式存储的 。
虽然事件溯源的践行者有不同的意见,但有不少人都认为事件溯源不是微服务的集成方式,而是微服务的一种内部实现方式 。因此,在一个系统中,可以某些微服务用事件溯源,另外一些微服务用数据库 。
当你要集成这些微服务时,你可以用事件通知的方式 。注意现在有两种不同的事件需要区分开,一种是微服务的内部事件,是颗粒度比较细的,这种事件只发送到这个微服务的stream中,只被事件溯源使用 。


推荐阅读