深度剖析分布式事务之 AT 与 XA 对比
AT
AT 这种事务模式是阿里开源的seata主推的事务模式,本文会详解AT的原理,并将它与XA模式进行比较
AT 从原理上面看,与 XA 的设计有很多相近之处。XA 是数据库层面实现的二阶段提交, AT 则是应用/驱动层实现的二阶段提交。建议您了解了XA相关的知识后,来阅读这篇文章,这样能够更快更好的掌握 AT 的原理与设计。
XA | AT | |
---|---|---|
脏回滚 | 无 | 存在 |
SQL支持度 | 全部支持 | 部分支持 |
脏读 | 无 | 有 |
应用侵入性 | 无侵入 | 无侵入 |
性能 | 较低 | 较低 |
数据库支持 | 主流数据库都支持 | 理论上可扩展至NoSQL |
原理
AT的角色和XA一样分为3个,但是起了不一样的名称,大家注意分辨:
- RM 资源管理器,是业务服务,负责本地数据库的管理,与XA中的RM一致
- TC事务协调器,是seata服务器,负责全局事务的状态管理,负责协调各个事务分支的执行,相当于XA中的TM
- TM 事务管理器,是业务服务,负责全局事务的发起,相当于XA中的APP
AT的第一阶段为prepare,它在这一阶段会完成以下的事情
- RM侧,用户开启本地事务
RM侧,用户每进行一次业务数据的修改,假设是一个update语句,那么AT会做以下内容:
- 根据update的条件查询出修改前的数据,该数据称为beforeImage
- 执行update语句,根据,根据BeforeImage中的主键,查询处修改后的数据,该数据为AfterImage
- 将BeforImage和AfterImage保存到一张undolog表
- 将BeforeImage中的主键以及表名,该数据称为lockKey,记录下来,六代后期使用
RM侧,用户提交本地事务时,AT会做以下内容
- 将2.4中记录的所有lockKey注册到TC(即事务管理器seata)上
- 3.1中被注册处理会检查TC中,是否有存在冲突的主键+表名,如果有冲突,AT会睡眠等待后重试,没有冲突啧保存
- 3.1成功完成后,提交本地事务
如果AT的第一阶段所有分支都没有错误,那么会进行第二阶段的commit,AT会做以下内容
- TC会将当前这个全局事务所相关的lockKey删除
- TC通知当前这个全局事务相关的所有业务,告知全局事务已经成功,可以删除undolog中保存的数据
- RM接到通知后,删除undolog的数据
如果AT第一阶段有分支出错,那么会进行第二阶段的rollback,AT会做以下内容
- TC通知与当前这个全局事务相关的所有业务服务,告知全局事务失败,执行回滚
RM收到通知后,对本地数据的修改进行回滚,原理如下
- 从undolog抽取出修改前后的BeforeImage和AfterImage
- 如果AfterImage与数据库中记录的当前记录效验一致,那么使用BeforeImage中的数据覆盖当前记录
- 如果AfterImage与数据库中当前记录不一致,那么这时候发生了脏回滚,此时需要人工接入解决
- TC待全局事务所有的分支都完成了回滚,TC将此全局事务所有的lockKey删除
脏回滚
AT模式的另一个突出的问题是2.4的脏回滚难以避免.一下步骤能够触发该脏回滚
- 全局事务g1对数据A1进行修改V1->V2
- 零一个数据对数据A1进行修改V2->V3
- 全局事务g1回滚,发现数据行A1的当前数据为V3,不等于AFterImage中的V2
这个脏回滚一旦发生,那么分布式事务框架没有办法保证数据的一致性了,必须要人工介入处理。想要避免脏回滚,需要把所有对这个表的写访问,都加上特殊处理(在Seata的Java客户端中,需要加上GlobalLock注解)。这种约束对于一个上了一定规模的复杂系统,是非常难以保证的。
对于某跳数据进行更新操作,如果全局事正在进行,当某本地事务需要更新该数据时,需要使用@GlobalLock确保其不会对全局事务正在操作的数据进行修改
XA在数据库系统层面实现了行锁,原理与普通事务相同,一次一旦俩个事务访问同一行数据,那么后一个事务会阻塞,完全不会有脏回滚的问题
其他
sql支持
AT 模式并未支持所有的SQL,它的原理是在应用层解析SQL,然后根据不同的SQL生成BeforeImage和AfterImage,一方面不同的SQL可能需要采用不同的逻辑来生成这些Image,另一方面不同的数据库语法不同,因此不常见的SQL,AT可能不支持。
XA 是数据库层面支持的,因此对所有的DML SQL都支持,不会出现问题
侵入性
AT 在最简单的情况下,通过在代码中添加注解,就能够把分布式事务引入到应用中,因此很多人认为是无侵入的。但是前面给出的脏读,脏回滚,SQL支持等,是开发人员必须考虑的,并进行设计,否则引入了相关注解变成全局事务之后,发生这些问题,导致线上应用故障,产生的后果会更严重。因此AT的无侵入不是真正的无侵入,仅仅是表面上代码的“无侵入”,但是设计上“侵入”了(不可以用AT未支持的SQL),行为上“侵入”了(需要容忍脏回滚),还会“侵入”其他项目(如果其他项目也写了同一张表,也需要加GlobalLock注解)。
XA 没有前面的问题,它的侵入性很低,在Java语言中,也同样做到通过加注解,而不用修改Java代码就完成分布式事务的引入。
性能分析
从原理步骤看,XA事务性能高于AT
AT模式下,RM侧,上述原理执行sql如下
- 开启事务
- 查询BeforeImage数据
- 执行update
- 查询AfterImage数据
- 将BeforImage,AfterImage和AfterIUmage插入到undolog中
- 提交事务
- 事务完成后,删除BeforeImage和AfterImage
而XA模式下,执行的sql如下
- xa begin
- 执行update
- xa end
- xaprepare
- xa commit
从上述理论分析,XA 事务性能会大幅高于AT,应当可以在postgres数据库上验证出来;而mysql数据库,在当前的5.8版本上,由于xa prepare后,需要将当前连接断开才能够在其他连接上xa commit,所以会有一个重新创建连接的开销。
我们可以看到,最终的结果XA性能优于AT。如果未来Mysql完善了XA的实现,可以不用关闭当前连接也能够允许其他连接提交xa事务,那么XA的性能还能够提升一大截。
但AT和XA两种模式,由于数据锁在整个分布式事务期间的存在,降低了并发度,因此性能都低于其他模式。当您的并发度较高时,建议使用其他无全局锁的事务模式
数据库支持
AT 目前支持了多个主流数据库,而且从理论上看,也能够扩展到非SQL数据库,但目前暂未看到支持非SQL数据库的扩展。
- AT与Redis:虽然Redis也支持事务,但Redis的事务支持主要是通过lua脚本来做的,与传统数据库的Begin Transaction/Commit不一样,因此上述生成前后镜像的原理并不适用Redis,因此AT想要支持Redis会非常困难,目前未看到有这样的尝试
- AT与Mongo:Mongo的事务支持与Mysql类似,但Mongo的操作类型很多,而且在主键规范上面,与SQL数据库有很大不同,想要正确生成前后镜像的工作量庞大,目前未看到有这样的尝试
XA 模式则需要底层数据库支持,目前主流的数据库,Mysql,Postgres,Oracle等都已支持。如果分布式事务涉及mongo呢?这个时候需要考虑其他事务模式
AT如何写隔离
- 一阶段本地事务提交前,需要确保拿到全局锁
- 拿不到全局锁,不能提交本地事务
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
总结
mysql在版本5.6中,xa相关API存在bug。如果当前连接在xa prepare之后,连接断开,那么这个连接未完成的事务会被自动回滚。这样的bug导致mysql的XA模式是无法保证正确性的,在各种应用crash中,可能导致数据不一致。因此AT在mysql的5.6版本及更低版本使用中,是具有很高应用价值的。
另外部分大厂的数据库是禁止使用XA事务的,这种特定场景下,选型AT模式,也是合理的。
对于其他场景,建议优先考虑 XA 事务。
具体流程https://blog.csdn.net/m0_73849256/article/details/126990022
XA
简介
XA是由X/Open组织提出的分布式事务的规范,XA规范主要定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中扮演的是RM角色
XA一共分为俩段
第一阶段(prepare):即所有的参与者RM准备执行事务并锁住需要的资源。参与者ready时,向TM报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者(TM)确认所有参与者(RM)都ready后,向所有参与者发送commit命令。
目前主流的数据库基本都支持XA事务,包括mysql、oracle、sqlserver、postgre
执行阶段
- 可回滚:业务sql操作放在XA分支中进行,由资源对XA协议的支持来保证可回滚
- XA分支完成后,执行XA prepare,同样,由资源对XA协议的支持来保证持久化(即,之后任何意外都不会造成无法回滚的情况)
完成阶段
- 分支提交:执行XA分支的Commit
- 分支回滚:执行 XA 分支的 rollback
TCC和SAGA比较
TCC
TCC模型完全交由业务端实现,每个子业务都需要实现try-confirm-cancel接口,对业务侵入性很大。
资源锁定需要业务自主实现。
Try:尝试执行事务,完成业务检查,预留必要的资源。
confirm:真正执行业务,不做业务资源检查
cancel:释放try阶段预留的业务资源
SAGA
Saga模型把一个分布式事务拆分成多个本地事务,每个本地事务都有对应的执行模块和补偿模块(对应TCC的confirm和cancel),当任何一个事务失败时,可以通过调用补偿模块来进行恢复,达到事务最终一致性。
Saga事务隔离性,业务层自己处理,通过冻结资源或者应用层加锁
Saga补偿方式:
向后恢复:补偿所有已完成的事务,本质就是所有已完成的本地事务进行回滚操作
向前恢复:重试失败的事务,假设每个子事务最终都会成功
适合
- 业务流程长、业务流程多
- 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口