上一节我们讲解了常用的事务,也提及了Saga,这是在分布式环境下被经常使用的一种处理复杂业务和分布式事务的设计模式。本章我们的主要目标是编写一个简单版本的Saga处理器,不同于Seata框架中那种可独立部署的事务服务,我们所编写的Saga和业务集成在一起也不支持通过手画流程的方式实现,因为我们的目标是将Saga作为一种设计模式(不是框架)来使用,类似于您经常使用的“工厂”、“策略”等,重点学习它的思想,在真实项目中使用肯定是需要根据需求做二次加工的。而且,简单版本的优势就是足够简单,投入虽然不多但能从中获取的收益却很大。在代码演示后我们还会重点描述一下如何解决Saga事务的隔离性问题。
一、Saga种类说明
常用的Saga包含两类:协同式和编排式。前者把流程的走向与协调全盘由事务的参考者来完成,比如最简单的场景:下单同时对库存进行扣减,订单服务本地事务完成后就把事件消息发送给库存服务,库存服务如果本地事务处理失败则由它将回滚的消息发送给订单服务。虽然整个流程当中订单服务与库存服务并没有产生耦合,但由于没有一个总的事务协调者,一旦服务参与者多起来那业务流程的可理解性就非常差,出了问题也不好定位。第二类为编排型事务也是我们本章要主要介绍的,通过把Saga的执行顺序交由一个集中的Saga编排器,由它指挥并决策业务的流向,相对于协同式整个流程要清晰很多。除非您使用的是足够成熟的第三方的框架,要不然集中式Saga也可能会存在事务参与者不清晰的问题,比如本文我们要介绍的Saga就会有类似的问题,毕竟以个人的精力很多事情的确无法做到极致,有牺牲也很正常。基于消息式的Saga很多时候你并不知道谁订阅了消息,所有的消费情况都体现在代码中,非常不方便后续的业务扩展以及代码阅读。个人在使用的时候偷了一个懒:通过图形文档的方式说明整个事务的走向包括会由谁发送命令,由谁来订阅事件,也就是把流程的逻辑定义与代码进行了分离,好的方式当然是把这些信息作为元数据来用,谁都喜欢好的但付出的成本也很高的。况且我是在2015年开始使用Saga,我倒是想用Seata呢,没有啊。
二、基于编排式的设计思想
基于编排的模式设计思路我们在前文中已经大概介绍,那要如何实现呢?可参考如下图所示。这里使用了四个设计原则:1)方便起见Saga会和全局事务发起的一起部署,这样就不用花费精力考虑如何独立部署Saga服务;2)Saga所有的服务遵循了这样的一个模式:发送命令,接受事件。也就是说Saga只向外发布领域命令,事件参与者执行完本地事务后发送领域事件并由Saga进行订阅,各服务间使用消息队列进行解耦;3)Saga的实现也被分为应用服务和领域实体。所有的命令其实都是由Saga领域实体在处理事件后生成的,在应用服务中调用“RabbitMQ”的客户端进行发送;4)只存储命令消息,不存储事件。
三、代码实现
根据上述的模式说明您会发现发布命令与事件并不需要进行代码的说明,通过RabbitMQ的客户端即可搞定。由于我们并不会将Saga实现为一个框架,所以也不涉及到框架内部复杂的逻辑代码,那么唯一需要介绍的是Saga如何与业务集成,这也是为什么我为本章所起的标题是“Saga设计模式”。
1、命令分发与处理
我们把发送命令和事件的组件称之为“命令总线”或“事件总线”,这样的封装在使用的时候不用使用者(一般是工程师)考虑消息队列使用的各种细节也可以实现组件的复用,毕竟您使用Saga的场景可能不只一个。
熟悉RabbitMQ的朋友都知道想要让消费者正确的收到消息我们可以使用“Topic”模式,相当于给消息一个路由的策略,Exchange会根据Topic的名称把消息投递到某个队列中,而消费者通过