深度剖析分布式事务之 AT 与 XA 对比

AT

AT 这种事务模式是阿里开源的seata主推的事务模式,本文会详解AT的原理,并将它与XA模式进行比较

AT 从原理上面看,与 XA 的设计有很多相近之处。XA 是数据库层面实现的二阶段提交, AT 则是应用/驱动层实现的二阶段提交。建议您了解了XA相关的知识后,来阅读这篇文章,这样能够更快更好的掌握 AT 的原理与设计。

XAAT
脏回滚存在
SQL支持度全部支持部分支持
脏读
应用侵入性无侵入无侵入
性能较低较低
数据库支持主流数据库都支持理论上可扩展至NoSQL

原理

AT的角色和XA一样分为3个,但是起了不一样的名称,大家注意分辨:

  • RM 资源管理器,是业务服务,负责本地数据库的管理,与XA中的RM一致
  • TC事务协调器,是seata服务器,负责全局事务的状态管理,负责协调各个事务分支的执行,相当于XA中的TM
  • TM 事务管理器,是业务服务,负责全局事务的发起,相当于XA中的APP

AT的第一阶段为prepare,它在这一阶段会完成以下的事情

  1. RM侧,用户开启本地事务
  2. RM侧,用户每进行一次业务数据的修改,假设是一个update语句,那么AT会做以下内容:

    1. 根据update的条件查询出修改前的数据,该数据称为beforeImage
    2. 执行update语句,根据,根据BeforeImage中的主键,查询处修改后的数据,该数据为AfterImage
    3. 将BeforImage和AfterImage保存到一张undolog表
    4. 将BeforeImage中的主键以及表名,该数据称为lockKey,记录下来,六代后期使用
  3. RM侧,用户提交本地事务时,AT会做以下内容

    1. 将2.4中记录的所有lockKey注册到TC(即事务管理器seata)上
    2. 3.1中被注册处理会检查TC中,是否有存在冲突的主键+表名,如果有冲突,AT会睡眠等待后重试,没有冲突啧保存
    3. 3.1成功完成后,提交本地事务

如果AT的第一阶段所有分支都没有错误,那么会进行第二阶段的commit,AT会做以下内容

  1. TC会将当前这个全局事务所相关的lockKey删除
  2. TC通知当前这个全局事务相关的所有业务,告知全局事务已经成功,可以删除undolog中保存的数据
  3. RM接到通知后,删除undolog的数据

如果AT第一阶段有分支出错,那么会进行第二阶段的rollback,AT会做以下内容

  1. TC通知与当前这个全局事务相关的所有业务服务,告知全局事务失败,执行回滚
  2. RM收到通知后,对本地数据的修改进行回滚,原理如下

    1. 从undolog抽取出修改前后的BeforeImage和AfterImage
    2. 如果AfterImage与数据库中记录的当前记录效验一致,那么使用BeforeImage中的数据覆盖当前记录
    3. 如果AfterImage与数据库中当前记录不一致,那么这时候发生了脏回滚,此时需要人工接入解决
    4. TC待全局事务所有的分支都完成了回滚,TC将此全局事务所有的lockKey删除

脏回滚

AT模式的另一个突出的问题是2.4的脏回滚难以避免.一下步骤能够触发该脏回滚

  1. 全局事务g1对数据A1进行修改V1->V2
  2. 零一个数据对数据A1进行修改V2->V3
  3. 全局事务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如下

  1. 开启事务
  2. 查询BeforeImage数据
  3. 执行update
  4. 查询AfterImage数据
  5. 将BeforImage,AfterImage和AfterIUmage插入到undolog中
  6. 提交事务
  7. 事务完成后,删除BeforeImage和AfterImage

而XA模式下,执行的sql如下

  1. xa begin
  2. 执行update
  3. xa end
  4. xaprepare
  5. 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 模式要求的三个接口
Last modification:November 17, 2023
如果觉得我的文章对你有用,请随意赞赏