阿里妹导读:分布式事务中涉及的参与者分布在异步网络中,参与者通过网络通信来达到分布式一致性,网络通信不可避免出现失败、超时的情况,因此分布式事务的实现比本地事务面临更多的困难。本文归纳总结五种分布式事务解决方案,并剖析其特点。较长,同学们可收藏后再看。
文末福利:Java 程序员学习清单。
Atomicity:原子性,事务中的所有操作要么都成功执行,要么都取消执行,不能存在部分执行,部分不执行的状态。
Consistency:一致性,举个例子简单的理解就是,A、B 两个账户各有 100 元,无论两个账户并发相互转账多少次,两个账户的资金总额依然是 200 元。
Isolation:隔离性,并发事务之间的相互影响程度,隔离性也是分级别的:读未提交、读已提交、可重复读等。
Durability:持久性,事务完成后对数据的更改不会丢失。
跨数据库分布式事务
跨服务分布式事务
混合式分布式事务
RM (Resource Managers):资源管理器,提供数据资源的操作、管理接口,保证数据的一致性和完整性。最有代表性的就是数据库管理系统,当然有的文件系统、MQ 系统也可以看作 RM。
TM (Transaction Managers):事务管理器,是一个协调者的角色,协调跨库事务关联的所有 RM 的行为。
AP (Application Program):应用程序,按照业务规则调用 RM 接口来完成对业务模型数据的变更,当数据的变更涉及多个 RM 且要保证事务时,AP 就会通过 TM 来定义事务的边界,TM 负责协调参与事务的各个 RM 一同完成一个全局事务。
CRMs (Communication Resource Managers):主要用来进行跨服务的事务的传播。
TM 记录事务 commit 日志,并向所有 RM 发起事务提交指令。
RM 收到指令后,提交事务,释放资源,并向 TM 响应“提交完成”。
如果 TM 收到所有 RM 的响应,则记录事务结束日志。
记录事务 abort 日志,向所有 RM 发送事务回滚指令。
RM 收到指令后,回滚事务,释放资源,并向 TM 响应回滚完成。
如果 TM 收到所有 RM 的响应,则记录事务结束日志。
如果 TM 发现整个事务只涉及到一个 RM,那么就会将整个过程退化为一阶段提交。
如果 RM 收到的 AP 的数据操作是只读操作,那么它可以在阶段 1 就将事务完成并告知 TM 其不再参与阶段 2 的过程。会有脏读的风险。
如果 RM 在阶段1完成后,长时间等不到阶段 2 的指令,那么其可以自动提交或者回滚本地事务。这叫做 Heuristic Completion,注意这种场景有可能会破坏事务的一致性,产生异常。
TM 在阶段 1 中询问 RM 前宕机,恢复后无需做任何操作。
TM 在阶段 1 中询问 RM 后宕机,可能只有部分 RM 收到了阶段 1 的请求,因此此时需要向 RM 发起回滚请求。
TM 在阶段 1 中询问 RM 完毕,但是在就准备完成日志时宕机,因不清楚宕机前的事务协商的结果,因此恢复后需要向 RM 发起回滚请求。
TM 在阶段 1 中记录完毕事务准备完成日志后宕机,恢复后可以根据日志发起提交或者回滚的指令。
TM 在阶段 2 中记录 commit/abort 日志前宕机,恢复后可以根据日志发起提交或者回滚指令。
TM 在阶段 2 中记录事务结束日志前宕机,恢复后可以根据日志发起提交或者回滚指令。
TM 在阶段 2 中记录事务结束日志后宕机,恢复后无需做任何操作。
阶段 1 中,RM 有超时情况时,TM 按失败处理,给所有 RM 发送回滚指令。
阶段 2 中,RM 有超时情况是,TM 需要对超时的 RM 持续重复发送指令。
原子性:在 prepare 和 commit 阶段保证事务是原子性的。
一致性:XA 协议实现的是强一致性。
隔离性:XA 事务在完成之前一直持有资源的锁,所以可以做到写隔离。
持久性:基于本地事务实现,所以这一点没有问题。
confirm:不做任何业务检查,仅仅使用预留的资源执行业务操作,如果失败会一直重试。
cancel:取消执行业务操作,释放预留的资源,如果失败会一直重试。
订单系统创建商品订单
支付系统接受小明的支付
库存系统扣减产品库存
会员系统给小明账户增加会员积分
try:创建一个订单,状态显示为“待支付”
confirm:更新订单的状态为“已完成”
cancel:更新订单的状态为“已取消”
try:假设小明账户中有 1000 元,冻结小明账户中的 100 元,此时小明看到的余额依然是 1000 元。
confirm:将账户余额变为 900 元,并清除冻结记录。
concel:清除冻结记录。
try:假设库存中还生 10 本书,冻结其中的一本书,现实库存依然有 10 本书。
confirm:将剩余库存更新为 9 本书,并清除冻结记录。
cancel:清除冻结记录。
try:假设小明原因积分 3000 分,给小明账户预增加 10 积分,账户显示的积分依然是 3000 分。
confirm:将账户积分更新为 3010,并清除预增加记录。
cancel:清除预增加记录。
原子性:事务发起方协调各个分支事务全部提交或者全部回滚。
一致性:TCC 事务提供最终一致性。
隔离型:通过 try 预分配资源的方式来实现数据的隔离。
持久性:交由各个分支事务来实现。
允许空回滚:原因是异常发生在阶段 1 时,部分参与方没有收到 try 请求从而触发整个事务的 cancel 操作,try 失败或者没有执行 try 操作的参与方收到 cancel 请求时,要进行空回滚操作。
保持幂等性:原因是异常发生在阶段 2 时,比如网络超时,则会重复调用参与方的 confirm/cancel 方法,因此需要这两个方法实现上保证幂等性。
防止资源悬挂:原因网络异常导致两个阶段无法保证严格的顺序执行,出现参与方侧 try 请求比 cancel 请求更晚到达的情况,cancel 会执行空回滚而确保事务的正确性,但是此时 try 方法也不可以再被执行。
T1,T2,...,Tn:n 个事务全部执行成功了。
T1,T2,...,Ti,Ci,...,C2,C1:执行到第 i (i<=n) 个事务的时候失败了,则按照 i->1 的顺序依次调用补偿操作。如果补偿失败了,就一直重试。补偿操作可以优化为并行执行。
T1,T2,...,Ti (失败),Ti (重试),Ti (重试),...,Tn:适用于事务必须成功的场景,如果发生失败了就一直重试,不会执行补偿操作。
原子性:Saga 协调器可以协调事务链中的本地事务要么全部提交,要么全部回滚。
一致性:Saga 事务可以实现最终一致性。
持久性:基于本地事务,所以这个特性可以很好实现。
允许空补偿:网络异常导致事务的参与方只收到了补偿操作指令,因为没有执行过正常操作,因此要进行空补偿。
保持幂等性:事务的正向操作和补偿操作都可能被重复触发,因此要保证操作的幂等性。
防止资源悬挂:网络异常导致事务的正向操作指令晚于补偿操作指令到达,则要丢弃本次正常操作,否则会出现资源悬挂问题。
Saga 是不完美补偿,补偿操作会留下之前原始事务操作的痕迹,需要考虑对业务上的影响。
TCC 是完美补偿,补偿操作会彻底清理之前的原始事务操作,用户是感知不到事务取消之前的状态信息的。
TCC 的事务可以更好的支持异步化,但是 Saga 模式一般在补偿阶段比较适合异步化。
基于事务消息的方案
基于本地消息的方案
事务发起者预先发送一个事务消息。
MQ 系统收到事务消息后,将消息持久化,消息的状态是“待发送”,并给发送者一个 ACK 消息。
事务发起者如果没有收到 ACK 消息,则取消本地事务的执行;如果收到了 ACK 消息,则执行本地事务,并给 MQ 系统再发送一个消息,通知本地事务的执行情况。
MQ 系统收到消息通知后,根据本地事务的执行情况更改事务消息的状态,如果成功执行,则将消息更改为“可消费”并择机下发给订阅者;如果事务执行失败,则删除该事务消息。
本地事务执行完毕后,发给 MQ 的通知消息有可能丢失了。所以支持事务消息的 MQ 系统有一个定时扫描逻辑,扫描出状态仍然是“待发送”状态的消息,并向消息的发送方发起询问,询问这条事务消息的最终状态如何并根据结果更新事务消息的状态。因此事务的发起方需要给 MQ 系统提供一个事务消息状态查询接口。
如果事务消息的状态是“可发送”,则 MQ 系统向下游参与者推送消息,推送失败会不停重试。
下游参与者收到消息后,执行本地事务,本地事务如果执行成功,则给 MQ 系统发送 ACK 消息;如果执行失败,则不发送 ACK 消息,MQ 系统会持续推送给消息。
原子性:最终可以实现分支事务都执行或者都不执行。
一致性:提供最终一致性。
隔离性:不保障隔离性。
持久性:由本地事务来保证。
小明选择充值金额“50 元”,支付方式“支付宝”。
联通网上营业厅创建一个充值订单,状态为“支付中”,并跳转到支付宝的支付页面(此时进入了支付宝的系统中)。
支付宝验明确认小明的支付后,从小明的账户中扣除 50 元,并向联通的账户中增加 50 元。执行完毕后向 MQ 系统发送一条消息,消息的内容标识支付是否成功,消息发送允许失败。
如果消息发送成功,那么支付宝的通知服务会订阅到该消息,并调用联通的接口通知本次支付的结果。如果此时联通的服务挂掉了,导致通知失败了,则会按照 5min、10min、30min、1h、...、24h 等递增的时间间隔,间隔性重复调用联通的接口,直到调用成功或者达到预订的时间窗口上限后,则不再通知。这就是尽最大努力通知的含义。
如果联通服务恢复正常,收到了支付宝的通知,如果支付成功,则给账户充值;如果支付失败,则取消充值。执行完毕后给支付宝通知服务确认响应,确认响应允许失败,支付宝系统会继续重试。所以联通的充值接口需要保持幂等性。
如果联通服务故障时间很久,恢复正常后,已超出支付宝通知服务的时间窗口,则联通扫描“支付中”的订单,主动向支付宝发起请求,核验订单的支付结果。
蚂蚁金服团队开发的 XTS,金融云产品名称为 DTX。
阿里巴巴中间件团队开发的 TXC。
2014 年阿里巴巴就已经推出了分布式事务中间件产品 TXC (Taobao Transaction Constructor)。
2016 年,TXC 进行了云产品化改造,提供了阿里云的云版本,名字叫做 GTS (Global Transaction Service) 。
2019 年,GTS 宣布开源,开源项目的名字叫做 Seata。
全局事务依然是基于各个分支事务来完成。Seata Server 协调各个分支事务要么一起提交,要么一起回滚。
各个分支事务在运行时,Seata Client 通过对 SQL 执行的代理和拦截,通过解析 SQL 定位到行记录,记录下 SQL 执行前后的行数据快照,beforeImage 和 afterImage 共同构成了回滚日志,回滚日志记录在独立的表中。回滚日志的写入和业务数据的更改在在同一个本地事务中提交。
分支事务完成后,立即释放对本地资源的锁,然后给 Seata 协调器上报事务执行的结果。
Seata 协调器汇总各个分支事务的完成情况,生成事务提交或者回滚的决议,将决议下发给 Seata Client。
如果决议是提交事务,则 Seata Client 异步清理回滚日志;如果决议是回滚事务,则 Seata Client 根据回滚日志进行补偿操作,补偿前会对比当前数据快照和 afterImage 是否一致,如果不一致则回滚失败,需要人工介入。
AT 模式只支持基于 ACID 事务的关系数据库。
AT 模式是通过对 SQL 解析来完成的,对 SQL 语法的支持有限,使用复杂 SQL 时需要考虑兼容性。
目前不支持复合主键,业务表在设计时注意添加自增主键。
全局事务默认的隔离级别是读未提交,但是通过 SELECT...FOR UPDATE 等语句,可以实现读已提交的隔离级别。通过全局排它写锁,可以做到的隔离级别介于读未提交和读已提交之间。
原子性:完全支持。
一致性:只提供最终一致性支持。
隔离性:不完全保证,通常为了系统的吞吐和性能,会一定程度上放弃对隔离性的要求。
持久性:完全支持。
BA:Basic Availability,基本业务可用性。
S:Soft state,柔性状态。
E:Eventual consistency,最终一致性。
参考
[1]分布式事务中间件TXC(http://mw.alibaba-inc.com/product-txc.html)
[2]弹力设计之补偿事务(https://www.jianshu.com/p/8095001d79bb)
[3]分布式事务中间件ServiceComb
(http://servicecomb.apache.org/cn/docs/distributed-transactions-saga-implementation/)
[4]深入理解两阶段提交(https://sq.163yun.com/blog/article/165554812476866560)[5]Seata(https://seata.io/zh-cn/)
[6]TCC事务原理
(https://www.cnblogs.com/jajian/p/10014145.html)
[7]TCC事务异常场景(https://blog.csdn.net/dm_vincent/article/details/92432059
[8]Compensating Transaction Pattern
(https://docs.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction)
[9]基于消息的分布式事务(https://www.jianshu.com/p/04bad986a4a2)
[10]分布式事务概述(http://www.tianshouzhi.com/api/tutorials/distributed_transaction/383)
[11]初识Open/X XA
(https://www.jianshu.com/p/6c1fd2420274)
[12]DTP: XA Specification
(https://pubs.opengroup.org/onlinepubs/009680699/toc.pdf)
[13]DTP Model
(https://pubs.opengroup.org/onlinepubs/009249599/toc.pdf)
Java 程序员学习清单
Java 初、中、高级程序员必不可少的学习清单,含学习方法论及各阶段技能、资料、课程等。识别下方二维码,或点击文末“阅读原文”立即查看:
推荐阅读