与我一起学习微服务架构设计模式4—使用Saga管理事务
- 2019 年 10 月 30 日
- 笔记
微服务下的事务管理
微服务架构下的事务往往需要横跨多个服务,每个服务都有属于自己的私有数据库。传统的分布式事务管理并不是合适选择,需要使用Saga机制。
微服务架构对分布式事务的需求
每个服务都有自己的私有数据库,需要一种机制来保障多数据库环境下的数据一致性。
分布式事务的挑战
分布式事务管理的事实标准是XA,它采用两阶段提交保证事务中所有参与方同时完成提交,或失败时同时回滚。应用程序的整个技术栈需要满足XA标准。
问题:
- 许多新技术,如NoSQL数据库,Kafka等消息代理不支持XA标准。
- 其本质上都是同步进程间通信,这会降低分布式系统的可用性。
使用Sage模式维护数据一致性
Saga由一连串的本地事务组成,每一个本地事务负责更新它所在服务的私有库,使用异步消息来协调一系列本地事务。启动Saga时,协调逻辑必须选择并通知第一个Saga参与方执行本地事务,一旦事务完成,Saga协调选择并调用下一个Saga参与方。直到执行完所有步骤。
挑战:
- 缺乏事务的隔离性
- 发生错误时的回滚更改
Saga使用补偿事务来回滚所做出的改变
当Saga的步骤因违反业务规则而失败时,Saga必须通过执行补偿事务显式撤销先前所做的更新,它按照正常事务的反向顺序来执行补偿事务。
Saga的协调模式
协同式Saga
把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式进行沟通。
Saga实现基于发布/订阅的通信时考虑的问题:可靠的事件通信。
1、基于协同的Saga每一步都会更新数据库并发布一个事件,确保两者是原子的。考虑使用事务性消息
2、确保Saga参与方能够将接收到每个事件映射到自己的数据。解决方案是让Saga参与方发布包含相关性ID的事件
好处:简单、松耦合
弊端:
- 更难理解,逻辑分布在每个服务实现
- 服务之间循环依赖关系
- 紧耦合风险,每个参与方都需要订阅所有影响它们的事件
编排式Saga
把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方完成具体操作。参与方完成后,会给编排器发送一个答复消息。
状态机是建模Saga编排器的一个好方法。
服务必须使用事务性消息,以便自动更新数据库并发布消息。
好处:
- 更简单的依赖关系,不会有循环依赖
- 较少的耦合,只实现供编排器调用的API
- 改善关注点隔离,简化业务逻辑
弊端:
在编排器中存在集中过多的业务逻辑风险,可以通过设计只负责排序的编排器来避免此问题,并且不包含任何其他业务逻辑。
解决隔离问题
Saga缺乏ACID事务的隔离属性,可能导致数据库异常。
- 丢失更新:一个Saga覆盖另一个Saga所做的更新。
- 脏读:一个事务或者一个Saga读取了尚未完成的Saga所做的更新。
- 模糊或不可重复读:一个Saga的两个不同步骤读取相同的数据却获得了不同的结果,因为另一个Saga已经进行了更新。
Saga的结构
可补偿性事务: 可以使用补偿事务回滚的事务
关键性事务: Saga执行过程的关键点。若执行成功,Saga将一直运行到完成。
可重复性事务: 在关键性事务之后的事务,保证成功。
对策
语义锁
应用程序级的锁。可补偿性事务会在其创建或更新的记录中设置标志(如Order的*_PENDING状态),表示该记录未提交且可能发生更改。它会被可重复事务清除,表示Saga完成,或通过补偿事务清除,表示Saga发生回滚。
两种方法处理锁定情况:
1、执行失败且告诉客户端重试,易于实现,但客户端必须实现重试逻辑,更复杂点。
2、使其阻塞,直到Saga释放语义锁。这使得更新相同操作的Saga被序列化,减少了编程量,消除了客户端重试的负担,但应用必须管理锁,实现死锁检测算法。
交换式更新
把更新操作设计成可以按任何顺序执行的,即可交换的。如账户的借记和贷记。
悲观视图
重新排序Saga的步骤,以最大限度降低脏读导致的业务风险
重读值
防止丢失更新,以在覆盖数据之前验证它是否保持不变。未更改,则更新数据,若已更改,则Saga中止且可能重新启动。
版本文件
将更新记录下来,以便对它们重新排序。
业务风险评级
使用每个请求的业务风险来动态选择并发机制。如使用Saga执行低风险请求,使用分布式事务执行高风险请求(如涉及大量资金)
框架
Eventuate Tram Saga:一个用于编写Saga编排器的框架。
java达人