什么是 XA协议

在XA协议中包含着两个角色:事务协调者和事务参与者。

XA 规范 使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。
还有三阶段提交

什么是 Seata 的事务模式?

Seata 定义了全局事务的框架。全局事务 定义为若干 分支事务 的整体协调: 1.TM 向 TC 请求发起(Begin)、提交(Commit)、回滚(Rollback)全局事务。 2.TM 把代表全局事务的 XID 绑定到分支事务上。 3.RM 向 TC 注册,把分支事务关联到 XID 代表的全局事务中。 4.RM 把分支事务的执行结果上报给 TC。(可选) 5.TC 发送分支提交(Branch Commit)或分支回滚(Branch Rollback)命令给 RM。

Seata 分布式事务 XA 与 AT 全面解析 - 图1

Seata 的 全局事务 处理过程,分为两个阶段:

执行阶段 :执行 分支事务,并 保证 执行结果满足是 可回滚的(Rollbackable) 和 持久化的(Durable)。 完成阶段:根据 执行阶段 结果形成的决议,应用通过 TM 发出的全局提交或回滚的请求给 TC, TC 命令 RM 驱动 分支事务 进行 Commit 或 Rollback。

Seata 的所谓 事务模式 是指:运行在 Seata 全局事务框架下的 分支事务 的行为模式。准确地讲,应该叫作 分支事务模式。不同的 事务模式 区别在于 分支事务 使用不同的方式达到全局事务两个阶段的目标。即,回答以下两个问题:

执行阶段 :如何执行并 保证 执行结果满足是 可回滚的(Rollbackable) 和 持久化的(Durable)。 完成阶段:收到 TC 的命令后,做到事务的回滚 / 提交

那么什么是 Seata XA 模式?

XA 模式:在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。

执行阶段: 可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚 持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)

完成阶段: 分支提交:执行 XA 分支的 commit 分支回滚:执行 XA 分支的 rollback

以下是 XA 模式在 Seata 所定义的事务模式下的设计模型Seata 分布式事务 XA 与 AT 全面解析 - 图2

什么是 Seata AT(TXC) 模式

去年 1 月份,Seata 开源了 AT 模式。AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的 “业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

通过简介,其实可以发现 AT 模式的特点,只需关注自己的业务 sql,对业务无入侵的一种分布式事务模式。那么我们应该知道他是怎么对业务做到无入侵的?

AT 模式如何做到对业务的无侵入

AT 模式一阶段

  • 首先,在 Seata 的组件中,如果你想开启分布式事务,那么就应该在你的业务入口或者事务发起入口加上 @GlobalTransactional 注解
  • 如果你是 AT 模式就要做好数据源代理(seata1.0 后全面支持自动代理),并被 sqlsessionfactroy 使用(或者直接 jdbc 操作使用被代理数据源)

可以发现比较关键的异步,与其他模式的区别便是代理数据源,而代理数据源又有什么奥秘呢?

Seata 分布式事务 XA 与 AT 全面解析 - 图3
如上图所示,你的数据源被代理后,通过被 DataSourceProxy 代理后,你所执行的 sql,会被提取,解析,保存前镜像后,再执行业务 sql,再保存后镜像,以便与后续出现异常,进行二阶段的回滚操作。

AT 模式如何保证隔离性

首先我们拿到官网所展示的文档来更直观的描述:Seata 分布式事务 XA 与 AT 全面解析 - 图4
可以通过上图得出:

一阶段本地事务提交前,需要确保先拿到 全局锁 。

拿不到 全局锁 ,不能提交本地事务。

拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。

本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。

tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。

本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,

该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 ,如 tx2 等待所超时,那么 tx2 便回滚本地事务所以他不会产生脏数据。

AT 模式二阶段提交

二阶段如果是提交的话,因为 “业务 SQL” 在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

Seata 分布式事务 XA 与 AT 全面解析 - 图5

AT 模式二阶段回滚

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的 “业务 SQL”,还原业务数据。回滚方式便是用“before image” 还原业务数据;但在还原前要首先要校验脏写, 对比 “数据库当前业务数据” 和 “after image”, 如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理。

Seata 分布式事务 XA 与 AT 全面解析 - 图6
完整的 AT 在 Seata 所制定的事务模式下的模型图:Seata 分布式事务 XA 与 AT 全面解析 - 图7

为什么支持 XA?

首先我们应该从 AT 去做判断,为什么 Seata 有了 AT 模式还去做 XA 的支持

  • 从视角出发:首先,我们来总结下 AT 模式,首先所有的事物发起,都是从 TM(不仅 AT) 且数据的读已提交只能在应用中见效(用户自行开发的系统),对资源的查看,无法做到全方面 而 XA 可让资源也感知到自身已处于全局事务中,对资源的隔离性可由数据库本身来实现,满足 全局一致性
  • 从入侵性,数据库支持角度:业务无入侵的更彻底,少于 2 个服务的操作,仅使用本地事务即可满足一致性,而 AT 需要 全局锁来保证隔离性,所以无论是 1 个服务,单库的操作,还是 n 个服务都需要开启全局事务来保证 隔离性。对数据库的支持,如果 AT 需要支持 mysql,pgsql,oracle 以外的数据库,需要做适配,并且 对复杂 sql 的解析成本更大,开发效率低,支持的 sql 数量少,XA 可全方位支持数据库的 sql 语句 多语言支持,如果你有 java 应用已经使用了 seata xa 那么本地数据库已经帮我们保证了隔离 性,即便其余 seata 不支持的语言和 java 并行处理下,数据也不会出现不一致的情况。

为什么 Seata 要支持 XA 模式?

  • 数据锁定:在整个事务处理过程结束前,涉及数据都被锁定,读写都按隔离级别的定义约束起来。
    AT 模式使用 全局锁 保障基本的 写隔离,实际上也是锁定数据的,只不过锁在 TC 侧集中管理 解锁效率高且没有阻塞的问题,且 XA 本地数据库可能持有间隙锁,造成锁的粒度更大,锁定更多无辜数据
  • 死锁(协议阻塞):XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。如果没有一个靠谱的协调者存在,比如 abc 三个库的数据被二阶段决议为提交,此时 ab 收到的指令,提交后,c 库在收到指令后挂了,并没有提交 xa 事务,或者协调者没有做到二阶段重试,那么这个没有提交的 xa 事务将会一直 持有锁,造成死锁的局面
  • 性能差:性能的损耗主要来自两个方面:一方面,事务协调过程,增加单个事务的 RT;另一方面,并发事务数 据的锁冲突,降低吞吐。其实主要原因就是上面的阻塞跟数据锁定造成,因为 xa 的一阶段并非提交,如果一阶段都是提交的场景下,由于 At 模式的一阶段提交,at 的性能是优于 xa,因为它锁在 tc 一侧集中释放,无需多个库进行本地的锁释放

AT 与 XA 的关系

首先,我们要明确,无论是 AT 还是 XA,他们都是有利用到数据库自带的事务特性,来保证数据一致性和隔离性

比如 AT 一阶段提交和二阶段回滚,都是执行了本地事务。比如 XA 的一阶段和二阶段,也都是利用了数据库本身的事务特性

那么这样一样我们是否应该在数据库层面进行挖掘,AT 与 XA 的关系呢?

首先这个时候,我们肯定要从中找相同,与找不同。AT 首当其冲,他有个必须品,也就是 undolog 表,undolog,相 信了解数据库的同学肯定是知道。数据库有六种日志分别是:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、 慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)

那么数据库的 undolog 是做什么用的呢?undolog 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC)

可以发现数据库的 undolog 跟 seata at 模式的 undolog 的作用不谋而合,所以可以判断,at 模式的 undolog 就是把本地事务作用中的 undolog,利用他的原理,做到了分布式事务中,来保证了分布式事务下的事务一致性。

那么说完了 undolog,redolog 呢?

Redolog 的作用便是防止在发生故障的时间点,尚有脏页未写入磁盘,在重启 mysql 服务的时候,根据 redo log 进行 重做,从而达到事务的持久性这一特性。

那么为什么 Seata AT 模式没有看到 redolog 的存在?其实很简单,这个 redolog 被隐藏的很深,也就是 AT 模式的一阶段提交,让数据库作为我们的 redolog,保证一阶段的数据准确落盘。

这个时候是不是会想到 LCN 事务模式?他的 undolog 由数据库来保证,缺少了一个 redolog 的存在。其实大可不必思念 LCN 事务,解析到这里,如果把 AT 改为一阶段不提交,二阶段提交时,前镜像便是 undolog,后镜像便是 redolog, 也就是说 AT 其实就是一个不在数据库层面,按照数据库事务思想和实现原理的方式,做到了分布式中的事务一致性。

这时候讲到这里,XA 跟 AT 的关系应该一幕了然了,准确的说,其实应该说是分布式事务跟数据库本地事务的关系,可以说 XA 的缺点造成了 AT 模式的出生,锁在多侧(多个库),资源阻塞,性能差。

而 AT 就像为了把事务的实现决定权从数据库手中,放到了 Seata 中,自实现 sql 解析,自实现 undolog(redolog),既然我们没有 办法去直接优化数据库在分布式事务下的问题,那么不如创造一个新的模式,去其糟粕,取其精华。

Seata AT 与 XA 的优劣

其实上面零零碎碎也说了不少各自的优缺点,现在我们总结一下分 3 点来做比较

  • sql 支持
  • 隔离性
  • 侵入性

我们先讲第一点,由于上面我们总结了,其实 AT 就是一个自实现的 XA 事务,所以其实可以知道,AT 在 sql 支持上,是远不及利用本地事务的 XA 模式,既然 AT 需要做 sql 解析,那么背后的实现只能自己来解决,也就是靠 Seata 社区的贡献者们来贡献解决方案,这是一个长期性的关键问题,但是依然有很多用户选择了重写 sql,来获取 AT 事务模式的支持。在 sql 支持上 XA 无疑是完胜的

第二点隔离性,Seata AT 模式通过解析 sql 获取涉及的主键 id,生成行锁,也就是 AT 模式的隔离就是靠全局锁来保证,粒度细至行级,锁信息存储在 Seata-Server 一侧。XA 模式的隔离性就是由本地数据库保证,锁存储在各个本地数据库中。由于 XA 模式一旦执行了 prepare 后,再也无法重入这个 XA 事务,也无法跟其他 XA 事务共享锁。因为 XA 协议,仅是通过 XID 来 start 一个 xa 事务,本身它不存在所谓的分支事务说法,它本事就是一个 XA 事务而已,也就是说它只管它自己。这时候可能由同学有疑问了,为什么我在 branch_table 里看到里 XA 分支事务呢?其实这个问题根据上面的什么是 Seata 事务模式可以了解到,Seata 的事务模式就是由全局事务,分支事务,锁信息所组成。而 XA 的分支事务,仅仅是作为一个参与方的存在,也就是说这个 XA 分支在 Seata 定义中为分支事务,作为分支信息记录在案,方便宕机后也可以下发二阶段决议信息。而 AT 由于锁是自实现,也就相对 XA 来说,我只要知道用户 sql 涉及到的数据,是不是数据这个全局事务下的,只要是我默认他就可以使用这个锁,也就解决了重入问题。我们可以得出总结,XA 的隔离性是全局的,AT 的隔离性是更灵活且相对全局的 (保证所有对数据的写操作被 Seata 事务覆盖)。第三点,入侵性,通过我们以上的信息,其实可以发现,谁更底层,谁的入侵性更小,所以由数据库自身所支持的 XA 模式来说,无疑入侵最小,使用成本最低。

其实说到这里,大家可能会觉得 XA 模式怎么感觉比 AT 好这么多,虽然他不支持锁重入,但是我可以避免这个情况发生呀。这时候,我画个图,大家可能会比较理解Seata 分布式事务 XA 与 AT 全面解析 - 图8
上图中,右侧图 1 是 at 模式运行时,图 2 时 xa 模式运行时。可以很明显,xa 的阻塞带来的性能下降时非常厉害的,特别是你的分支事务非常多,每个资源的释放必须等到每个分支的数据库去单独释放,后续的事务才能进入。虽然 XA 带来的无侵入非常高,但是由于性能下降的程度太大,也就促使了 AT 的诞生,而现在 AT,TCC,SAGA 的模式的接受度也越来越高,这也正说明了开发者对性能的要求。AT 可以看作时由 Seata 社区进行全方面优化,自研的 XA 模式,最大特点就是解决了 XA 模式的性能差的问题。TCC 由 Seata 决定二阶段状态通知,其使用全权交托用户,性能仅仅是 2 个本地事务 + 些许 rpc 开销。SAGA 整个事务链路,事务处理全权交托用户编排,性能完全由用户来保证,Seata 作为事务的协助方,记录全局事务的运行状态。可以看出来,越高入侵性的模式其实背后可优化的点更多,越少入侵性的,也就是会被局限,只能依托组件开发者进行不定期的优化来保证性能。

引用


https://mp.weixin.qq.com/s/ges_CTXwNpv2dp_hS4PvSA