了解 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 模式为主线的机制:
- 一阶段:RM 解析业务 SQL → 查询前后镜像 → 与业务数据同一本地事务写入 undo_log → 注册分支到 TC → 提交本地事务(不长时间占连接,这是 AT 高吞吐的关键)→ 上报状态;
- XID 传播:全局事务 ID 通过 RPC 拦截器(Feign 的 header、Dubbo 的 attachment)自动透传;跨 MQ 要手动放进消息属性——断链是常见事故;
- 二阶段:提交 → 异步批量删 undo_log(几乎零成本);回滚 → RM 校验数据后反向补偿。
隔离性设计(重点):一阶段本地已提交,全局事务之间靠 TC 侧全局锁(表加主键粒度)串行化写冲突——拿不到锁的本地事务重试等待;默认读未提交,读隔离需 @GlobalLock 或配置。
架构师风险清单:undo_log 与业务同库(容量与清理策略);全局锁热点行等于吞吐瓶颈;XID 断链产生游离分支(重点测 MQ 与线程池场景);TC 要集群加 DB 主备。知道这些边界,才知道 AT 什么时候会咬人。
💻 全栈开发专家
以主流 AT 模式讲机制:
一阶段:RM(数据源代理 DataSourceProxy,JDBC 层自动代理)拦截业务 SQL:
- 解析 SQL,查前镜像(before image)
- 执行业务 SQL(本地事务内)
- 查后镜像(after image)
- 前后镜像 + SQL 信息写 undo_log 表(与业务数据同库同事务提交)
- 提交前向 TC 申请该行的全局锁(表名+主键维度),拿不到就重试或回滚——防两个全局事务改同一行造成脏写
- 本地事务提交,一阶段即释放本地锁和连接(AT 高性能的关键:不长期占数据库资源)
二阶段:
- 全局提交:分支早已提交,TC 异步通知 RM 直接删 undo_log,几乎零成本
- 全局回滚:RM 按 undo_log 生成反向 SQL 补偿;补偿前校验当前数据与后镜像一致(被人改过就转人工)
XID 传播:TM 开启全局事务生成 XID,跨服务经 Feign/HTTP header(TX_XID)/MQ 属性传播,下游自动绑定为同一全局事务的分支。
排查抓手:io.seata DEBUG 日志看 XID 注册与锁竞争;undo_log 表残留数据就是回滚异常的现场。