微服务设计模式 - 04 使用 Saga 管理事务
导读
- 为什么分布式事务不适合现代应用程序
- 使用 Saga 模式维护微服务架构的一致性
- 使用协同和编排这两类方式协调 Saga
- 采用对策来解决缺乏隔离的问题
Spring 使用 @Transactional 注解来让方法调用自动的在事务范围内完成, 在只访问一个数据库的单体应用中, 事务管理简单明了.
微服务架构下, 服务往往都有自己的私有数据库, 在这种情况下, 应用程序必须使用一些高级的事务管理机制来管理事务.
你需要一种机制来保证多数据库环境下的数据一致性.
分布式并不简单, 它的本质是进程间通信,这会降低分布式系统的可用性, 为了让一个分布式事务提交完成,所有参与事务的服务都必须可用.
分布式理论 CAP 早已证明, 系统只能在它的一致性,可用性和分区容错性 三个属性中保证两个, 今天架构师更强调可用性而放弃数据强一致性.
所以, 实际上, 分布式事务并不是一个好的选择,为了解决这个问题, 需要构建 松耦合,异步服务之上的 Saga.
Saga 模式: 通过使用异步消息来协调一系列本地事务,从而维护多个服务之间的数据一致性.
这个Saga 包含了以下几个本地事务:
1. Order Service: 创建一个处于 approval_pending 状态 order.
2. Consumer Service: 验证当前订单中的消费者可以下单.
3. Kitchen Service: 验证订单内容,并创建后厨 ticket, 状态为 create_pending.
4. accounting service: 对消费者提供的信用卡做授权操作.
5. Kitchen Service: 把后厨工单 ticket 状态改为 awaiting_acceptance.
6. Order Service: 把 Order 状态改为 approved.
当本地事务完成时, 服务会发布消息. 然后,此消息将触发 Saga 中的下一个步骤, 使用消息不仅可以确保 Saga 参与方之间的松散耦合, 还可以保证 Saga 完成. 这是因为如果消息的接收方暂时不可用, 则消息代理会缓存消息, 直到消息被投递为止. 目前我们碰到两个问题:
Q1. Saga 之间缺乏隔离.
缺乏隔离可能会导致 3 个问题:
Saga 事务包含 3 类:
语义锁: Saga 的可补偿性事务会在其创建或更新的任何记录中设置标志. 该标志表示该记录未提交且可能更改. 该标志可以是阻止其他事务访问记录的锁.
Q2. 发生错误时的回滚更改.
Saga 按照正常事务的反向顺序来执行补偿事务(回滚)
那么 Saga 是如何做到的呢?
- 协同式: 把 Saga 的决策和执行顺序逻辑分布在 Saga 的每一个参与方中, 它们通过交换事件的方式来进行沟通.
- 编排式: 把 Saga 的决策和执行顺序逻辑集中在一个 Saga 编排器类中, Saga 编排器发出命令式消息给各个 Saga 参与方, 指示这些参与方服务完成具体操作.(本地服务)
协同式
好处:
简单: 服务在创建, 更新和删除业务对象时发布事件.
松耦合: 参与方订阅事件并且彼此之间不会因此产生耦合. (失败事件也要发布)
弊端:
更难理解: 没有定义 Saga, 不知道哪些事务是 combine 在一个 Saga 中的.
服务之间的循环依赖关系: Saga 参与方订阅彼此的事件, 这通常会导致循环依赖(这通常不是一个好的设计风格)
编排式 Saga (通常更推荐)
当使用 Saga 编排式时, 开发人员定义一个编排器类, 这个类的唯一职责就是告诉 Saga 的参与方该做什么事情. Saga 编排器采用命令/异步响应方式与Saga参与方通信, 为了完成 Saga 中的一个环节, 编排器对某个参与方发出一个命令式的消息, 告诉这个参与方该做什么操作, 当参与方服务完成操作后, 会给编排器发送一个答复消息, 编排器处理这个消息, 并决定 Saga 的下一步操作.
把 Saga 编排器视为一个状态机.
状态机由一组状态和一组事件触发的状态之间的转换组成. 每个转换都可以有一个动作, 对 Saga 来说动作就是对某个参与方的调用, 状态之间的转换由 Saga 参与方执行的本地事务完成触发, 当前状态和本地事务的特定结果决定了状态转换以及执行的动作. 对状态机也有有效的测试策略, 因此,使用状态机模型可以更轻松哦的设计,实现,测试 Saga.