根据服务架构,事务可以大致划分为三类
- 本地事务:一个服务使用一个数据源
- 全局事务:一个服务使用多个数据源
- 分布式事务:涉及多个服务,不同服务使用不同数据源
本地事务
由数据库提供支持,如MySQL中的InnoDB存储引擎,提供了ACID实现。
全局事务
涉及到多个数据源,需要从外部提供支持。XA(eXtended Architecture)事务处理架构就是为此而生的,其核心内容是定义一个全局的事务管理器(用于协调全局事务)和局部的资源管理器(用于驱动本地事务)之间的通信接口。XA 接口是双向的,能在一个事务管理器和多个资源管理器之间形成通信桥梁,通过协调多个数据源的一致动作,实现全局事务的统一提交或者统一回滚。
XA
XA 将事务提交拆分成为两阶段过程:
- 准备阶段:又叫投票阶段,协调者询问事务的所有参与者是否准备好提交,参与者如果已经准备好提交则回复 Prepared,否则回复 Non-Prepared。对于数据库来说,准备操作是在重做日志中记录全部事务提交操作所要做的内容,它与本地事务中真正提交的区别只是暂不写入最后一条 Commit Record 而已。
- 提交阶段:又叫作执行阶段,协调者如果在上一阶段收到所有事务参与者回复的 Prepared 消息,则先自己在本地持久化事务状态为 Commit,在此操作完成后向所有参与者发送 Commit 指令,所有参与者立即执行提交操作;如果任意一个参与者回复了 Non-Prepared 消息,或任意一个参与者超时未回复,协调者将自己的事务状态持久化为 Abort 之后,向所有参与者发送 Abort 指令,参与者立即执行回滚操作。对于数据库来说,commit操作是很轻量的,仅仅是持久化一条 Commit Record 而已,只有收到 Abort 指令时,需要根据回滚日志清理已提交的数据,这可能是相对重负载的操作。
这个过程也被称为两阶段提交(2 Phase Commit,2PC),这种方式其实存在一些问题
- 单点问题:协调者宕机后,无法工作
- 性能问题:所有参与者被当作一个整体,需要等到所有操作结束才算完成,这种方式,可想而知,性能较差。
- 一致性风险:在协调者进行commit操作后宕机,无法向参与者发出commit指令。这会导致事务数据不一致,协调者已提交,参与者未提交。
因为这些问题的存在,不如将全局事务转换为分布式事务进行处理。
分布式事务
由于涉及多个服务,多个数据源,无法由单个节点提供事务支持,必须由外部提供支持。这其实就是分布式系统中的一致性,事务必须要保证一致性,但是有强一致性和弱一致性-最终一致性的区别。
可靠事件队列
基本思想是通过最终一致性的方式来实现分布式事务。具体方案是,多个服务之间通过消息队列连接,以消息消费的形式进行接口调用。在发生异常时,重复消费直至成功,或者在失败几次后,记录下来进行手动确认。
在这个方案中,需要保证接口的幂等性,因为网络原因可能重复消费消息,消息系统需要保证消息的不丢失。
SAGA事务
SAGA提出了一种提升“长时间事务”(Long Lived Transaction)运作效率的方法,思路是把一个大事务分解为可以交错运行的一系列子事务集合。
原本 SAGA 的目的是避免大事务长时间锁定数据库的资源,后来发展成将一个分布式环境中的大事务分解为一系列本地事务的设计模式。SAGA 由两部分操作组成。
- 大事务拆分若干个小事务,将整个分布式事务 T 分解为 n 个子事务,命名为 T(1),T(2),…,T(i),…,T(n)。每个子事务都应该是或者能被视为是原子行为。如果分布式事务能够正常提交,其对数据的影响(最终一致性)应与连续按顺序成功提交 T(i)等价。
- 为每一个子事务设计对应的补偿动作,命名为 C(1),C(2),…,C(i),…,C(n)。T(i)与 C(i)必须满足以下条件:
- T(i)与 C(i)都具备幂等性。
- T(i)与 C(i)满足交换律(Commutative),即先执行 T(i)还是先执行 C(i),其效果都是一样的。
- C(i)必须能成功提交,即不考虑 C(i)本身提交失败被回滚的情形,如出现就必须持续重试直至成功,或者要人工介入。
如果 T(1)到 T(n)均成功提交,那事务顺利完成,否则,要采取以下两种恢复策略之一:
- 正向恢复(Forward Recovery):如果 T(i)事务提交失败,则一直对 T(i)进行重试,直至成功为止(最大努力交付)。这种恢复方式不需要补偿,适用于事务最终都要成功的场景,譬如在别人的银行账号中扣了款,就一定要给别人发货。正向恢复的执行模式为:T(1),T(2),…,T(i)(失败),T(i)(重试)…,T(i+1),…,T(n)。
- 反向恢复(Backward Recovery):如果 T(i)事务提交失败,则一直执行 C(i)对 T(i)进行补偿,直至成功为止(最大努力交付)。这里要求 C(i)必须(在持续重试后)执行成功。反向恢复的执行模式为:T(1),T(2),…,T(i)(失败),C(i)(补偿),…,C(2),C(1)。
SAGA 必须保证所有子事务都得以提交或者补偿,但 SAGA 系统本身也有可能会崩溃,所以它必须得有高可靠和高可用设计。在宕机恢复后仍然可以追踪到子事务的执行情况,譬如执行至哪一步或者补偿至哪一步了。
严谨的保证正向或者逆向恢复不是容易的,所以SAGA 事务一般不会直接靠裸编码来实现,而是借助分布式事务中间件来完成,例如Seata。
参考资料
凤凰架构:构建可靠的大型分布式系统 | 凤凰架构 (icyfenix.cn)