Seata RM的事务提交与回滚源码解析
文章目录
- 前言
- 一、RM提交事务
- 二、RM回滚事务
- 2.1、undo校验逻辑
- 2.2、执行回滚逻辑
- 总结
- RM 的事务提交与回滚行为说明(基于 Seata AT 模式)
- 1. 提交阶段(Phase Two Commit)
- 2. 回滚阶段(Phase Two Rollback)
前言
当TC接收到TM的提交/回滚请求后,在处理好自身相关的逻辑后,会向每一个分支事务RM发送请求,驱动RM执行实际的提交/回滚业务逻辑:
微服务端处理对应请求,是在io.seata.rm.AbstractRMHandler
中:
一、RM提交事务
DataSourceManager
的branchCommit
是处理事务提交的方法:
调用的是asyncWorker
的方法,AsyncWorker
是DataSourceManager
一个用于异步处理任务的组件:
在AsyncWorker
的构造中,会创建一个固定大小的线程池执行doBranchCommitSafely
定时任务。
最终会调用到addToCommitQueue
方法,首先尝试将任务放入队列的尾部,如果队列已满,会返回false,不会抛出异常。然后会通过CompletableFuture
异步编排机制,先通过scheduledExecutor
执行doBranchCommitSafely
的逻辑,然后再次执行addToCommitQueue
。
如果当前的队列已满,commitQueue.offer(context)
返回的就是false。会先从队列中消费元素,然后再尝试将本次任务加到队列中。那如果队列未满,也就是commitQueue.offer(context)
返回的是true。那么就直接return了,任务什么时候会执行?在上文中提到,构造AsyncWorker
时,会启动一个定时任务调用addToCommitQueue
方法。
所以重点需要关注doBranchCommitSafely
的逻辑,而该方法最终会调用doBranchCommit
,在该方法中:
- 使用
drainTo()
方法将commitQueue
中当前所有任务一次性取出,放入allContexts
列表中。 - 把取出来的
Phase2Context
根据resourceId
进行分组。 - 遍历每一组同一资源 ID 下的提交任务,调用 =
dealWithGroupedContexts()
逐组提交,实际提交过程发生在dealWithGroupedContexts()
方法中。
在得到数据库代理连接对象,以及undolog后,执行删除undolog并且提交事务的逻辑:
删除undolog,并且提交分支事务,如果出现异常,则回滚,并且调用addAllToCommitQueue
再次放入队列。
这里为什么要调用
addAllToCommitQueue
再次放入队列?
因为这里的提交事务,以及操作,都是针对删除undolog表的,不是针对业务代码的。业务代码事务的提交,是在执行业务代码时的。TC驱动RM提交事务,此时执行的操作是处理undolog,而不是提交业务代码的事务。也就不会存在业务代码存在bug,重试一百次依旧失败的场景。这里的 重试仅仅针对删除undolog失败的场景,捕获的异常类型也仅仅是SQL异常。
但是当前 addAllToCommitQueue() 没有内置重试次数限制,所以如果某条记录永远删不掉,会进入“无限重试”风险;实际部署中需要搭配告警机制(日志报警、慢提交监控)来识别这种问题;
二、RM回滚事务
DataSourceManager
的branchRollback
是处理事务回滚的方法:
在undo
方法中,会根据当前分支id和xid,去查询对应的undolog。
执行回滚相关逻辑的核心代码:undoExecutor.executeOn(connectionProxy);
2.1、undo校验逻辑
在executeOn
的dataValidationAndGoOn
方法中,会对前置镜像和后置镜像进行校验,这里涉及到脏数据回滚:
在dataValidationAndGoOn
方法中,首先会取出前置镜像和后置镜像。然后进行比较:前置镜像和后置镜像相同,说明数据没有发生修改,也就不需要回滚。
然后会获取数据库中的当前记录,和后置镜像比较:
- 如果当前记录和后置镜像相同,证明没有其他方法对该条数据进行修改。所以可以回滚。
- 如果当前记录和后置镜像不相同,还需要进行判断:
- 前置镜像和当前数据相同,证明虽然当前记录被其他方法修改了,但是修改后的值和前置镜像,也就是执行事务之前的值是一样的,相当于变相地已经回滚了,所以无需再次回滚。
- 前置镜像和当前数据不相同,这种情况证明当前记录被其他方法修改了,不能回滚。比如某样商品原本的库存是1000,本次事务将其改成了700,但是另一个方法手动将其改成了800。那么现在读取到的数据是800,和前置镜像后置镜像都不一样,这种情况不能简单地回滚到1000。
2.2、执行回滚逻辑
undoPST.executeUpdate();
会执行回滚的逻辑:
然后会执行deleteUndoLog
删除undolog表:
总结
RM 的事务提交与回滚行为说明(基于 Seata AT 模式)
1. 提交阶段(Phase Two Commit)
在全局事务的一阶段中,业务 SQL 已经通过本地事务提交完成,Spring 的事务已结束。随后,RM 通过异步机制清理事务日志:
- 实际操作:删除
undo_log
表中与本次事务相关的日志记录。 - 实现机制:
- 使用阻塞队列存储待处理的提交上下文(
Phase2Context
); - 借助定时任务线程池(AsyncWorker)周期性调度任务;
- 结合
CompletableFuture
异步编排机制,确保即使提交队列满时也能自动重试。
- 使用阻塞队列存储待处理的提交上下文(
- 容错设计:若日志删除失败,将记录异常,并自动重试。删除失败不会影响业务数据的一致性。
2. 回滚阶段(Phase Two Rollback)
若全局事务在协调器(TC)层被判定为失败,RM 将执行回滚操作。回滚行为基于一阶段写入的 undo_log
执行“反向补偿”,具体流程如下:
- 读取 undo_log 中记录的前镜像和后镜像;
- 获取当前数据库的实时数据;
- 进行数据一致性校验,决定是否执行回滚,判断逻辑如下:
前镜像 vs 当前数据 | 后镜像 vs 当前数据 | 处理方式 |
---|---|---|
相同 | 相同 | 不执行回滚(数据未被修改) |
不同 | 相同 | 执行回滚 |
不同 | 不同 | 抛出异常(数据已被篡改) |
相同 | 不同 | 不执行回滚(幂等性处理) |
注:若当前数据与后镜像不同,但与前镜像一致,说明业务重试或幂等行为已覆盖,回滚可安全跳过。