Seata AT 模式的工作原理
Seata 的 AT (Automatic Transaction) 模式 是 Seata 默认且最常用的分布式事务模式。它的核心思想是“两阶段提交协议(2PC)的演进”,最大的特点是对业务无侵入(业务代码无需为了分布式事务编写补偿逻辑),通过代理数据源自动完成事务的协调。
下面为您详细拆解 Seata AT 模式的工作原理。
一、 核心组件与前提条件
在理解原理之前,先回顾 Seata 的三大核心组件:
- TC (Transaction Coordinator) - 事务协调者: 独立的 Seata 服务端,维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器: 嵌入在业务应用中,负责开启全局事务,并最终发起全局提交或全局回滚的决议。
- RM (Resource Manager) - 资源管理器: 嵌入在业务应用中,负责管理本地数据库资源,与 TC 交涉注册分支事务和报告状态,并接收 TC 的指令执行第二阶段。
AT 模式的前提条件:
- 必须是支持本地 ACID 事务的关系型数据库(如 MySQL、Oracle 等)。
- 业务应用必须使用 Seata 的 JDBC 数据源代理(
DataSourceProxy)。 - 每个业务数据库中必须创建一张特定结构的表:
undo_log(回滚日志表)。
二、 AT 模式的两阶段工作流程
AT 模式将分布式事务分为两个阶段:执行阶段(Phase 1) 和 决议阶段(Phase 2)。
第一阶段(Phase 1):执行并提交本地事务
在这一阶段,Seata 会拦截并解析业务 SQL,记录数据修改前后的状态,并与业务 SQL 在同一个本地事务中提交。这样可以快速释放数据库的本地锁。
具体步骤:
- 解析 SQL: Seata 的数据源代理拦截到业务 SQL(例如
UPDATE user SET balance = balance - 100 WHERE id = 1),解析出表名、条件、修改的字段等。 - 查询前置镜像(Before Image): 在执行 SQL 前,Seata 根据解析出的条件,查出修改前的数据状态。
- 执行业务 SQL: 执行真正的业务更新操作。
- 查询后置镜像(After Image): 在执行 SQL 后,再次查询该行数据,获取修改后的数据状态。
- 生成回滚日志: 将 Before Image 和 After Image 以及业务 SQL 信息组装成一条回滚日志(Undo Log)。
- 申请全局锁: (关键步骤) 在本地事务提交前,RM 会向 TC 申请该记录的全局锁(由 表名 + 主键 组成)。如果拿不到全局锁,本地事务将不断重试,直到超时失败回滚。
- 本地事务提交: 将 业务数据的修改 和 回滚日志(Undo Log 的插入) 在同一个本地数据库事务中提交。
- 汇报状态: 本地事务提交成功后,RM 向 TC 汇报分支事务执行成功。
阶段一总结: 业务数据和回滚日志被一起提交到数据库,本地锁已被释放,极大地提高了并发性能。
第二阶段(Phase 2):全局决议(提交或回滚)
当 TM 所在的微服务执行完所有业务逻辑后,会根据是否有异常发生,向 TC 发起全局提交或全局回滚的请求。TC 收到请求后,会通知所有相关的 RM 执行第二阶段。
情况 A:全局提交(Global Commit)
如果整个微服务调用链路都没有报错:
- TC 通知各个 RM 提交分支事务。
- RM 收到提交指令后,由于第一阶段业务数据已经提交到了数据库,所以第二阶段只需清理
undo_log表中的对应记录即可。 - 这个清理过程是放入内存队列中异步批量执行的,非常快,对业务几乎无影响。
情况 B:全局回滚(Global Rollback)
如果链路中某个微服务报错,TM 发起全局回滚:
- TC 通知各个 RM 回滚分支事务。
- RM 收到回滚指令后,通过分支事务 ID 找到对应的
undo_log记录。 - 数据校验(脏写校验): RM 取出
undo_log中的 After Image(即自己第一阶段修改后的值),与当前数据库里的实际值进行比对。- 如果一致,说明没有被其他事务篡改过,允许回滚。
- 如果不一致,说明发生了“脏写”(有非 Seata 事务直接改了数据库),此时会触发异常报警,需要人工介入处理。
- 还原数据: 校验通过后,根据 Before Image 生成反向 SQL(例如把
UPDATE改回原来的值),并执行。 - 清理日志: 删除该条
undo_log记录。 - 提交本地回滚事务: 将反向 SQL 和清理日志操作在一个本地事务中提交。
- RM 向 TC 报告分支回滚完成。同时释放 TC 端的全局锁。
三、 隔离性保障 (Isolation)
在分布式环境下,多个事务并发修改同一条数据会带来隔离性问题。Seata AT 模式是如何解决的?
1. 写隔离 (Write Isolation)
Seata 通过 全局锁(Global Lock) 来保证写隔离。
- 全局锁的获取: 阶段一本地事务提交前,必须获取 TC 的全局锁。
- 阻止脏写: 只要全局事务还没结束(未进行 Phase 2),TC 里的全局锁就不会释放。其他 Seata 事务如果想修改同一行数据,在阶段一提交前会发现全局锁被占用,从而等待。
- 防非 Seata 事务脏写: 如果是普通的 JDBC 事务绕过 Seata 直接改数据库,Seata 怎么防?这就是前面提到的 Phase 2 回滚时的 After Image 校验机制,发现被改了就会拦截回滚并报警。
2. 读隔离 (Read Isolation)
- 默认隔离级别: AT 模式默认的全局隔离级别是 读未提交(Read Uncommitted)。因为阶段一本地事务已经提交了,其他本地事务是可以查到修改后的数据的。
- 如何实现“读已提交(Read Committed)”? 如果业务对一致性要求很高,不能读到中间状态,可以使用
SELECT ... FOR UPDATE语句。Seata 的代理层会拦截这个语句,在查询前要求先获取 全局锁。如果全局锁被其他未完成的分布式事务占着,这个查询就会阻塞等待,从而实现了全局的读已提交。
四、 AT 模式的优缺点
优点:
- 零代码侵入: 开发者只需要使用
@GlobalTransactional注解,写正常的 JDBC 代码即可,不需要像 TCC 模式那样自己去写 Try/Confirm/Cancel 三个方法。 - 性能较高: 相比于传统的 XA 强一致性事务,AT 模式在第一阶段就把本地数据库锁释放了,极大缩短了数据库锁的持有时间,提高了并发吞吐量。
缺点:
- 特定数据库依赖: 依赖关系型数据库的 ACID 特性,且 Seata 需要实现对应数据库方言的 SQL 解析器(目前支持 MySQL、Oracle、PostgreSQL 等主流 RDBMS)。
- 全局锁竞争: 如果业务存在极度集中的“热点数据”更新(如扣减同一个热门商品的库存),多个分布式事务争抢同一个行记录的全局锁会导致性能瓶颈。
- 最终一致性: 它不是像 XA 那样的强一致性,在 Phase 1 之后、Phase 2 之前,存在短暂的数据不一致窗口期(本地数据已改,但全局事务未结束)。
总结
Seata AT 模式本质上是一个基于代理数据源、通过记录回滚日志来实现自动补偿的、非侵入式的两阶段提交架构。它巧妙地平衡了分布式事务的易用性和性能,是微服务架构中最常采用的分布式事务落地方案。
右滑查看面试常问