深入剖析Seata如何实现分布式事务(主要基于AT模式)

一、Seata核心组件剖析

深入剖析Seata 的架构与核心组件、以及它实现分布式事务(AT/TCC/SAGA/XA 等模式)底层原理,大致分为:总体架构→核心组件角色→全局事务生命周期→各模式(AT / TCC / SAGA / XA)底层实现细节→容错/恢复与权衡结论。

1. 总体概览

Seata 是一个独立的事务协调服务(TC) + 嵌入式客户端(TM、RM)的分布式事务框架,支持多种事务模式(AT、TCC、SAGA、XA),把"全局事务协调"与"资源(数据/业务操作)管理"职责拆分,从而在微服务场景下实现多库/多服务的事务一致性。 (seata.apache.org, GitHub)

2. 核心组件(角色)与职责

  • TC(Transaction Coordinator / Server)

    • 独立部署的协调者,负责维护全局事务会话(global session)、分支事务状态、全局锁信息(lock_table)、驱动全局提交或回滚、恢复(retry/recover)逻辑。TC 提供持久化存储(file/db/redis 等模式)以便故障恢复。 (seata.apache.org)
  • TM(Transaction Manager,客户端 API)

    • 嵌入在业务侧,通常即发起全局事务的那一端(@GlobalTransactional 注解层)。TM 向 TC 请求开始/提交/回滚全局事务并传播 XID。 (GitHub)
  • RM(Resource Manager,数据/资源侧)

    • 嵌入在业务侧(通常以 DataSourceProxy、RPC hook 等形式),负责把本地事务作为"分支事务"注册到 TC(branch register)、上报分支状态、执行分支提交/回滚。RM 在不同模式下的实现有差异(AT 会写 undo_log;XA 用底层 XA;TCC 由业务实现 try/confirm/cancel)。 (seata.apache.org)
  • 运维/控制台(Console)

    • 提供查看事务、强制回滚/提交、查看锁等运维能力(对线上故障恢复很重要)。 (书堆)
  • 持久化表(典型:global_table / branch_table / lock_table / undo_log)

    • 用于 TC/RM 的状态持久化与回滚数据存储(AT 模式下每个参与 DB 都需要 undo_log 表)。 (CSDN博客, seata.apache.org)

3. 全局事务的典型生命周期(关键步骤)

  1. TM 请求 TC 开启全局事务 → TC 生成 XID(全局事务 ID)。

  2. XID 透传到下游服务 / 模块。

  3. 每个参与服务的 RM 在本地执行本地事务(JDBC、业务本地操作等),并向 TC 注册分支事务(branch register)。

  4. 业务本地事务提交/回滚(通常是先执行本地操作),并把分支状态报告给 TC(部分模式下由 TC 主导最终决策)。

  5. 当 TM 发起全局提交时,TC 根据所收集的所有分支状态发起全局提交或回滚:TC 向所有分支下发 Commit / Rollback 指令,收集结果并完成清理(例如删除分支/全局会话、释放锁)。

    这个流程的具体细节(谁先释放本地锁、是否写 undo 日志、是否需要业务代码支持)依赖于所用的事务模式。上述通用流程及 XID/分支注册机制可在官方/源码文档中看到。 (GitHub)

4. 各模式的底层原理(重点)

4.1 AT(Automatic Transaction)模式 ------ Seata 最常用、非入侵式的模式

核心思想(如何模拟分布式 2PC):

  • 在 JDBC 层做拦截代理(DataSourceProxy) ,在本地事务提交前把"回滚所需的信息(before/after image)"序列化并写入本地数据库的一张 undo_log 表;同时向 TC 注册分支并申请全局写锁(由 TC 管理的全局锁与本地的 SELECT FOR UPDATE 联动),本地事务提交后释放 DB 本地锁但保留 undo_log。

  • 当 TC 下发全局提交 时,RM 异步删除 undo_log(完成清理);当 TC 下发全局回滚 时,RM 根据 undo_log 中的 before-image 做逆向 SQL 恢复(reverse SQL),并做脏数据校验(比较 undo_log 中的 after-image 与当前数据库,若不一致则说明数据已被其他事务改过,按策略处理,可能需要人工干预)。因此 AT 模式用 undo_log + 全局锁 + DataSource 代理 来"模拟"分布式两阶段提交的效果,但避免在 prepare 阶段长期持有 DB 资源从而提升吞吐。 (seata.apache.org, 思否)

关键实现点/细节

  • undo_log:保存 xidbranch_idrollback_info(before/after image 的序列化 blob)等,用于回滚时还原。每个参与的 DB 都需要该表。 (seata.apache.org)

  • 全局锁(global lock):在执行 SELECT FOR UPDATE 时,RM 会检查是否持有全局锁;若别的全局事务持锁,RM 会重试 / 阻塞,保证写隔离(Seata 将全局写锁概念与本地行锁结合)。这是一种由 TC 管理的写排他策略,用来减少并发冲突。 (seata.apache.org)

  • 提升点与局限:AT 非侵入(业务无需改很多代码),吞吐通常优于 XA(因为短时间释放本地 DB 连接),但对复杂 SQL、并发冲突与"脏数据检测"逻辑需要注意。 (seata.apache.org)

4.2 TCC(Try-Confirm-Cancel)模式 ------ 业务驱动式的 2PC

核心思想: 每个参与者由业务自己实现三段式接口:try(资源预留/准备)、confirm(最终提交)、cancel(回滚补偿) 。TC 负责:第一阶段调用 Try 并注册分支,第二阶段根据全局决策顺序调用 confirm 或 cancel。
适用场景/特征: 适用于对性能和隔离有严格要求、且业务能易于实现幂等/补偿逻辑的场景;代价是需要改业务代码(实现 confirm/cancel)。 (seata.apache.org, 阿里云)

4.3 SAGA 模式 ------ 长事务的补偿链

核心思想: 把长事务拆为一系列本地事务串联执行,每个本地事务都需要提供一个"正向业务"与对应的"补偿(compensate)"操作;一旦中间出错,系统按反向顺序执行补偿动作。TC 在 Saga 模式下负责记录执行顺序与触发补偿(或基于状态机重放)。这是最终一致性、非阻塞的解决方案,但补偿逻辑复杂并需要业务自行保证补偿幂等与正确性。 (seata.apache.org)

4.4 XA 模式 ------ 标准的资源管理器级 2PC(依赖底层 DB 支持)

核心思想: 使用 X/Open XA 协议,由数据库/资源管理器本身提供 prepare/commit/rollback;Seata 做上层协调(TM/TC/RM 注册/上报),在某些数据库(如 MySQL)上 Seata 做了对标准流程的优化,减少阶段数,但 XA 的特点是 强隔离但锁持有时间长 (prepare 后资源被占用,性能和可用性上有代价)。适合传统迁移场景或数据库本身支持 XA 的场景。 (seata.apache.org, 阿里云)

5. 故障、恢复与一致性保证(Seata 如何保证"最终完成/回滚")

  • 持久化与恢复 :TC 的会话(global/branch/lock)持久化(file/db/redis 模式),TC 崩溃后可以通过持久化数据重建会话并做恢复(retry)操作;这对可用性/HA 的选择很关键(file 模式仅单机适用,db/redis 可用于集群)。 (seata.apache.org, 博客园)

  • 回调重试与异步工作队列 :TC/RM 使用工作队列进行异步清理(比如 AT 模式在 commit 后异步删除 undo_log),并有重试机制来处理网络抖动 / 临时失败。 (seata.apache.org, 思否)

  • 脏数据检测 :AT 回滚前会对 undo_log 中记录的 after-image 与当前 DB 进行对比,若不一致说明有并发写入,需要根据策略(报警/人工/特殊合并策略)处理,避免误回滚破坏别人的修改。 (seata.apache.org, 思否)

6. 关键数据结构与表(快速索引)

  • undo_log(各参与 DB,AT 模式必需) --- 保存回滚信息。 (seata.apache.org)

  • global_table / branch_table / lock_table(TC 存储,全局会话/分支/锁信息)。 (CSDN博客, seata.apache.org)

7. 设计权衡

  • AT 的优点 :对业务侵入小、吞吐较高(不在 prepare 阶段长时间占用 DB 连接)。缺点是需要 undo_log、脏数据检查、对复杂 SQL(如批量、触发器)可能有额外要求。 (seata.apache.org, 思否)

  • TCC 的优点 :语义明确、无 undo 日志,适合高并发但需要业务配合的场景;缺点是开发复杂度高(需要实现 try/confirm/cancel 并保证幂等)。 (seata.apache.org)

  • XA 的优点 :标准、强隔离(数据库层面做好),缺点是性能与锁等待时间长。 (seata.apache.org)

  • SAGA 的优点 :无长事务阻塞,适合业务流程型场景;缺点是补偿逻辑(正确性与幂等)复杂。 (seata.apache.org)

二、基于Seata AT 模式的典型场景分析

一个最常见的场景(下单 -> 扣库存) 作为例子,用按时间顺序的细粒度步骤把 Seata AT 模式底层在每一步到底做了什么、哪个组件负责、以及这些动作如何一起保证分布式事务的一致性。

场景简述(示例):用户下单(OrderService)要做两件事:

  1. order 表插入一条订单;

  2. inventory 表扣减 SKU 库存(InventoryService)。

    两个服务各自用独立 MySQL,业务希望这两步要么同时成功,要么都回滚 ------ 用 Seata AT 模式 来保证一致性。官方 AT 原理与实现说明参见。 (seata.apache.org)


1. 全局时序总览

复制代码
sequenceDiagram
  participant TM as OrderService(TM)
  participant RM1 as OrderService(RM/DataSourceProxy)
  participant RM2 as InventoryService(RM/DataSourceProxy)
  participant TC as Seata-TC

  TM->>TC: GlobalBegin() => XID
  TM->>RM1: 执行业务SQL(insert order)(XID透传)
  RM1->>TC: branchRegister(lockKeys?, resourceId) => branchId
  RM1->>DB1: 执行 SQL, 写undo_log(before/after) & 写业务数据 (同一本地事务)
  RM1->>TM: localCommit
  TM->>RM2: 调用扣库存(XID透传)
  RM2->>TC: branchRegister(...) => branchId
  RM2->>DB2: 执行 SQL,写undo_log & 写业务数据
  RM2->>TM: localCommit
  TM->>TC: GlobalCommit(XID)
  TC->>RM1: branchCommit
  TC->>RM2: branchCommit
  TC: delete locks & mark global finished

2. 逐步拆解 Seata 在底层做了什么(并说明为什么能保证一致性)

2.1 预置(业务需要做的)

  • 在所有参与数据库上建 undo_log 表(AT 模式必需);把业务应用的 DataSource 用 Seata 的 DataSourceProxy 或 auto-proxy 包裹(这样 Seata 才能拦截 SQL,生成 undo_log,收集锁信息并与 TC 交互)。这些是使用 AT 的先决条件。 (seata.apache.org)

2.2 TM 发起全局事务(GlobalBegin -> 生成 XID)

  • 动作 :应用(OrderService)通过 Seata 客户端(TM)调用 TC 的 globalBegin,TC 创建一个全局会话并返回一个 XID(全局事务 id)。

  • 为什么重要 :XID 是全局事务的唯一标识,后续每个分支都会用这个 XID 注册到 TC,从而把多个本地事务关联成一个全局事务。 (seata.apache.org)


2.3 业务代码在本地执行 SQL 构成"分支" --- DataSourceProxy 拦截(关键)

  • 动作(核心) :当 OrderService 执行 INSERT order ... 或 InventoryService 执行 UPDATE inventory SET qty=qty-1 WHERE sku=? 时,Seata 的 DataSourceProxy/ConnectionProxy 拦截 JDBC 调用。Proxy 会:

    1. 解析 SQL (获得表、主键、where 条件等),尝试构造可用于回滚的 beforeImage(更新前的行)与 afterImage(更新后的行)。

    2. 构造 lockKey(行级写锁的标识,后续要向 TC 注册/竞争)并把 lockKey 放入当前 connection/transaction 的上下文(ConnectionContext)。

    3. (必要时)向 TC 注册分支(branchRegister) ------ 具体时点有细微差别(见下一小节)。

    4. 把 undo 信息写入 undo_log (这是最关键的一步之一):Seata 序列化 beforeImage/afterImage 并以 undo_log 记录(包括 xid, branchId, rollback_info 等),并与业务数据写入同一个本地事务(同一个 DB TX)。

  • 为什么重要 :把 undo_log 与业务写一起提交,保证了"要么业务成功并有对应 undo_log(方便回滚),要么两者都失败"。因此当 TC 后续决定回滚时,RM 能基于 undo_log 做逆向恢复。官方把 AT 描述为"通过代理层记录 before/after image 与全局锁来模拟分布式 2PC"。 (seata.apache.org, GitHub)

关于 branchRegister(分支注册)的时机

  • DataSourceProxy 会在能获得 lockKey 的时刻 注册分支(例如对 SELECT FOR UPDATE / 明确可以推断 lock keys 的 update/delete),也可能在 本地事务 commit 之前 统一注册(如果无法提前推断),这取决于 SQL 类型与代理的检测逻辑。总之,TC 必须知道每个分支需要的 lockKeys,以便实现全局写排他。 (seata.apache.org)

2.4 本地事务提交(业务 DB)------"先本地提交,再等 TC 指令" 的关键设计

  • 动作:RM 将业务表的写、undo_log 的写都在一个本地事务里提交(即本地提交成功后,业务数据已写入,undo_log 也同时存在)。然后本地事务结束,释放本地 DB 行锁(短时间持有,避免长期占用 DB 资源)。

  • 为什么重要 :与传统 XA 的"prepare 阶段要占用资源直到 commit/rollback"不同,Seata 的做法是在本地尽量短时间提交,以提高吞吐(避免长事务占用 DB 连接/锁)。但同时保留 undo_log 以支持后续的全局回滚。官方把这叫做"把业务数据和撤销日志写在一个本地事务里,然后尽快释放连接与行锁"。 (seata.apache.org, 思否)


2.5 TC 收到分支注册与锁信息,建立锁(全局锁管理)

  • 动作 :当 RM 注册了分支并把 lockKey 提交给 TC,TC 会在自己的锁表(lock_table 或内存/持久化结构)上登记该 XID/branch 所占用的 lockKey ;TC 会拒绝其它试图占用相同 lockKey 的全局事务(或让它排队/重试)。锁是由 TC 集中管理(而不是持续占用 DB 行锁),以达成写隔离语义。 (阿里云, GitHub)

  • 保证点:TC 的锁表保证同一资源(行)不会被两个活跃全局事务并发提交,从而避免写冲突导致的不一致。


2.6 所有分支本地都成功提交后,TM 向 TC 发起全局提交(GlobalCommit)

  • 动作 :TM 调 globalCommit(XID),TC 将该全局事务的状态置为 "committing" 并开始二阶段驱动:对每个已注册的分支(在 branch_table 里)下发 branchCommit 指令(RPC 到各 RM)。

  • 为什么重要 :TC 在知道所有分支都已本地提交(或都已注册)后,才统一发出 commit 指令,避免部分提交 / 部分回滚的不一致。 (seata.apache.org)


2.7 分支收到 commit:本地清理(异步删除 undo_log / 释放 TC 锁)

  • 动作(分支 commit 阶段)

    • RM 收到 branchCommit 后:

      1. 在 AT 模式,真正要做的"提交"往往是:删除 undo_log(因为业务数据已是最终状态,不再需要回滚记录);删除undo_log 的操作可由 RM 异步执行(TC 也会触发清理任务)。

      2. 向 TC 报告branchFinish(成功/失败)。

    • TC 在收到所有分支 branchCommit 的成功回执后,会把全局 session 清理(删除 global/branch 会话、释放全局锁)。 (思否, GitHub)

  • 为什么重要:undo_log 的删除意味着该分支的"回滚可能性"被放弃(commit 完成),而 TC 清理锁则允许后续事务获得这些资源。


2.8 若有任意一分支或 TM 报错 -> TC 发起全局回滚(GlobalRollback)

  • 动作 :TC 发出 branchRollback 给所有已注册分支(或由分支主动上报错误并触发 TC 决策)。

  • 分支如何回滚(核心) :RM 查 undo_log(按 xid + branchId),取出 beforeImageafterImage,由 Seata 的reverse-sql 引擎 生成逆向 SQL(例如把 update 的 afterImage 反向更新为 beforeImage,或把 insert 做 delete),并在本地事务内执行这些 reverse SQL,从而把数据库恢复到分支操作前的状态。若回滚过程中发现当前 DB 的实际值与 undo_log 的 afterImage 不一致(说明有人在本地事务提交后篡改过数据),Seata 会报警/做脏数据策略(需要人工或者特殊策略处理),以避免误伤别人的变更。 (GitHub, 阿里云)


2.9 防止资源"悬挂"的细节(log_status、幂等与重试)

  • 场景:分支在注册后但本地事务还没来得及写入 undo_log 就发生了全局回滚(race),如果简单回滚可能找不到 undo_log -> 存在资源悬挂或数据不一致风险。

  • Seata 的应对 :引入 undo_log.log_status 等机制:当发现回滚来得太早、undo_log 未写入时,Seata 在回滚路径会插入一个特殊的 log_status=1 的占位记录,保证后续本地提交(试图写 undo_log)会因为唯一索引冲突失败,从而避免"丢失 undo_log 导致的悬挂"。此外 TC/RM 有大量重试/补偿任务 (recover worker)来保证最终一致性。 (blog.junxworks.cn, 书堆)


3. 总结"为什么 AT 能保证一致性"的五条关键理由

  1. undo_log(回滚镜像)+ 与业务同一本地事务写入 :保证了"要么有业务数据 + undo_log,要么两者都没有",从而在需要回滚时能精确恢复本地数据。 (GitHub)

  2. TC 的全局锁(lock keys)集中管理写并发 :同一资源不会被两个活跃全局事务并发提交,避免写冲突。 (阿里云)

  3. 短事务本地提交 + 后续中心协调(相比 XA 减少了长锁占用) :通过尽快提交本地事务释放 DB 资源,同时保留 undo_log 做补偿,权衡了性能与一致性。 (seata.apache.org, 思否)

  4. 逆向 SQL 引擎 + recover/retry 机制 :在回滚时用 undo_log 完成反向恢复,并通过 TC 的定期恢复任务(重试/补偿)保证在节点故障/网络抖动时仍能最终完成。 (GitHub)

  5. 若遇到极端 race(undo 未写就回滚)有防悬挂策略(log_status 占位)与人工介入通道 :避免少数竞态导致的资源丢失或悬挂。 (blog.junxworks.cn)


4. 常见注意点 / 限制

  • 必须使用 JDBC(关系型 DB)并能让 Seata 代理 DataSource ,AT 模式依赖 SQL parsing 与 undo_log,非关系型或不经 JDBC 的操作无法自动回滚。 (seata.apache.org)

  • 复杂 SQL(批量更新、trigger、store-procedure)可能使 before/after image 难以精确 ,建议测试并谨慎对待。 (seata.apache.org)

  • 隔离级别与 lock 行为要理解 (例如使用 SELECT ... FOR UPDATE 可以显式产生行锁,影响 branchRegister 时机)。 (seata.apache.org)

  • undo_log 表和 TC 的持久化配置(file/db/redis)直接影响故障恢复能力 ,生产建议用可靠的持久化后端并启用 TC 的恢复 worker。 (seata.io)


5. 便于追踪排查的日志表

  • TC sideglobal_tablebranch_tablelock_table(查看哪些全局/分支/锁还活着)。 (GitHub)

  • RM side :参与库的 undo_log(查看 xid/branch_id/rollback_info),并查看 log_status 字段(帮助判断悬挂等异常)。 (blog.junxworks.cn)

  • 客户端/应用日志:DataSourceProxy 的拦截日志、branchRegister/branchReport 的 RPC 日志、TC 的 recover 日志。

相关推荐
Rookie小强3 小时前
ZooKeeper和Reids做分布式锁的区别?
分布式·zookeeper·云原生
斯普信专业组3 小时前
zookeeper因jute.maxbuffer启动异常问题排查处理
分布式·zookeeper·云原生
啥都不懂的小小白4 小时前
Dubbo从入门到实战:分布式服务开发指南
分布式·dubbo
真上帝的左手4 小时前
十、软件设计&架构-分布式-分布式事务
分布式·架构
Zfox_6 小时前
Redis应⽤-缓存与分布式锁
服务器·数据库·redis·分布式·缓存
老四敲代码18 小时前
Kafka 生产者与消费者分区策略全解析:从原理到实践
分布式·kafka
会飞的架狗师19 小时前
【Kafka系列】第二篇| Kafka 的核心概念、架构设计、底层原理
分布式·kafka
lifallen20 小时前
Hadoop MapReduce过程
大数据·数据结构·hadoop·分布式·apache
婷儿z1 天前
部署 Zabbix 企业级分布式监控
分布式·zabbix