Skip to content

了解 Seata 的实现原理吗? ​

  • 标签:Seata、Spring Cloud、微服务

🏛 业务架构师 ​

Seata AT 模式的实现原理,我按"一拦截、两镜像、三阶段"讲:

1. 数据源代理(无侵入的秘密):Seata 对 JDBC DataSource 做代理,业务 SQL 执行时被拦截解析——业务代码完全无感知,这就是"一个注解搞定"的底层支撑。

2. 两阶段协议:

  • 一阶段:业务 SQL 与 undo_log 在同一本地事务提交。提交前记录数据修改前镜像(before image)和修改后镜像(after image)进 undo_log 表,并向 TC 注册分支、申请全局行锁(防止其他全局事务改同一行)。一阶段就把本地事务释放了——不长时间占数据库连接,这是它比 XA 快的关键;
  • 二阶段提交:TC 通知各分支异步删除 undo_log 即可(最快路径);
  • 二阶段回滚:RM 校验 after image 与当前数据一致(没被别人改过),用 before image 反向生成补偿 SQL 恢复数据,释放全局锁。

3. 隔离性:默认读未提交(全局视角),需要读已提交时用 @GlobalLock + select for update——AT 是最终一致倾向的方案,这是选型时要向业务方讲清楚的语义。

架构师视角的两个实战警示:

  • 全局锁是吞吐天花板:热点行(爆款商品库存)被多个全局事务串行竞争,吞吐骤降——热点场景改 TCC/异步扣减/缓存预售,不硬扛 AT;
  • undo_log 与对账兜底:回滚失败(数据被脏改)会悬挂全局事务,必须有异常事务巡检 + 人工修复流程——框架保不住的部分,运营流程来补。

🏗 系统架构师 ​

以 AT 模式为主线的机制:

  1. 一阶段:RM 解析业务 SQL → 查询前后镜像 → 与业务数据同一本地事务写入 undo_log → 注册分支到 TC → 提交本地事务(不长时间占连接,这是 AT 高吞吐的关键)→ 上报状态;
  2. XID 传播:全局事务 ID 通过 RPC 拦截器(Feign 的 header、Dubbo 的 attachment)自动透传;跨 MQ 要手动放进消息属性——断链是常见事故;
  3. 二阶段:提交 → 异步批量删 undo_log(几乎零成本);回滚 → RM 校验数据后反向补偿。

隔离性设计(重点):一阶段本地已提交,全局事务之间靠 TC 侧全局锁(表加主键粒度)串行化写冲突——拿不到锁的本地事务重试等待;默认读未提交,读隔离需 @GlobalLock 或配置。

架构师风险清单:undo_log 与业务同库(容量与清理策略);全局锁热点行等于吞吐瓶颈;XID 断链产生游离分支(重点测 MQ 与线程池场景);TC 要集群加 DB 主备。知道这些边界,才知道 AT 什么时候会咬人。

💻 全栈开发专家 ​

以主流 AT 模式讲机制:

一阶段:RM(数据源代理 DataSourceProxy,JDBC 层自动代理)拦截业务 SQL:

  1. 解析 SQL,查前镜像(before image)
  2. 执行业务 SQL(本地事务内)
  3. 查后镜像(after image)
  4. 前后镜像 + SQL 信息写 undo_log 表(与业务数据同库同事务提交)
  5. 提交前向 TC 申请该行的全局锁(表名+主键维度),拿不到就重试或回滚——防两个全局事务改同一行造成脏写
  6. 本地事务提交,一阶段即释放本地锁和连接(AT 高性能的关键:不长期占数据库资源)

二阶段:

  • 全局提交:分支早已提交,TC 异步通知 RM 直接删 undo_log,几乎零成本
  • 全局回滚:RM 按 undo_log 生成反向 SQL 补偿;补偿前校验当前数据与后镜像一致(被人改过就转人工)

XID 传播:TM 开启全局事务生成 XID,跨服务经 Feign/HTTP header(TX_XID)/MQ 属性传播,下游自动绑定为同一全局事务的分支。

排查抓手:io.seata DEBUG 日志看 XID 注册与锁竞争;undo_log 表残留数据就是回滚异常的现场。