微服务下的事务管理
微服务架构下的事务往往需要横跨多个服务,每个服务都有属于自己的私有数据库。传统的分布式事务管理并不是合适选择,需要使用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达人
(长按或扫码识别)
感谢你的阅读,下面是一个抽奖链接按钮,10月27日晚上开奖,共5个红包,写文不易,感谢大家支持。
感谢大家一直以来的阅读、在看和转发,点我参与抽奖!点我参与抽奖!