⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/weishenmedachangdouqiaoqiaofangqile-Seata/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

工程师有个通病:新框架 Demo 跑通、文档好看、Star 够多,就拍板上线。等生产出事了,才开始真正读那份文档。

我用 Seata 18 个月,然后亲手删了它。

2022 年初,订单系统刚拆完微服务。下单链路横跨订单、库存、积分三个库,数据一致性方案讨论了两个月没结论。

然后我看到了 Seata。加一行 @GlobalTransactional,分布式事务自动搞定,对业务透明。Demo 十分钟跑通,我当场拍板:就用这个。

现在回头看,「十分钟跑通」应该是第一个警报信号。

先搞清楚 Seata 在背后干了什么

三个角色:TM 发起全局事务,RM 管各服务的本地资源,TC 居中协调——这个 TC 是独立部署的,所有分布式事务都要经过它。

Seata 三角色架构图,来源:seataapacheorg

AT 模式原理不复杂:每执行一条 SQL,Seata 前后各拍一张数据快照存进 undo_log。提交就清掉快照,回滚就用快照还原。

Seata AT 模式两阶段提交流程,来源:seataapacheorg

关键点来了——全局事务未提交期间,涉及到的行会被加上全局锁。 这一条,文档里有写,但埋在一大堆概念说明里。我当时一扫而过,没当回事。

后来才明白:这把锁就是所有问题的根源。

500 QPS 就崩了,这性能你敢上生产?

全链路压测,QPS 拉到 500,DBA 打电话过来问我在搞什么。

我拉出监控一看:订单接口 P99 从 80ms 飙到 640ms,数据库锁等待堆了一屏。

原因很清楚:热门商品库存那一行,500 个请求全在排队等全局锁释放。 每个请求还得跟 TC 打一次网络往返(约 20ms)。锁持有时间被网络延迟拉长,后面的请求全在干等。

压测数据摆在这,没有辩解空间:

QPS P99 DB 锁等待 结论
100 82ms 无 ✅ 岁月静好
300 160ms 偶发 ⚠️ 开始抖了
500 640ms 大量 ❌ DBA 打电话
800 超时 死锁 ❌ 直接熔断

结论:AT 模式 + 热点数据 + 高并发 = 必死。 文档里说了?说了。我看了?看了。我当回事了?没有。

换 TCC?恭喜,代码量翻三倍

第一反应是换 TCC 模式。TCC 不走全局锁,理论性能好得多。

代价是每个接口要写三个方法:Try(预占资源)、Confirm(确认提交)、Cancel(回滚释放)。

让小陈去改,两周后提了 PR。我打开一看——Cancel 里有一坨防御性代码,空回滚、幂等校验、悬挂处理,三个异常场景每个接口都要覆盖。

我问:三个服务十七个接口,全要这样?

他说:对。

我在工位上坐了五分钟,想明白了一件事:我当初选 Seata 就是因为它”不侵入业务”。结果 AT 模式性能不行,TCC 模式直接把业务逻辑的复杂度翻了三倍。 不管走哪条路,”简单透明”这个初心已经不存在了。

核心链路上挂了个全局单点,谁给的勇气

11 月某个周三下午 3 点,线上报警不是接口变慢——是所有涉及分布式事务的请求全部挂起。

TC 集群有个节点网络抖动,30 秒。

30 秒足以让订单接口超时、连接池打满、用户下单全部失败。故障持续 4 分钟。

复盘时我画了张架构图,把 TC 用红笔圈出来。那一刻我真正理解了一件事:

所有微服务的分布式事务都经过 TC。它不是”协调者”,它是”命门”。一个节点抖一下,整个交易链路断掉。 而且 TC 集群的运维复杂度不亚于 ZooKeeper——选举、数据同步、会话超时,全要你自己兜着。

DBA 甩了张截图:undo_log 18G

DBA 某天下午在群里贴了张截图,一句话没说:

undo_log | 18,432 MB

逻辑链是这样的:全局锁 → 事务持有时间长 → undo_log 记录来不及清理 → 表越涨越大 → 清理任务追不上写入 → 正反馈循环。

18G 的 undo_log,占用的不只是磁盘。每次查询都要在这张膨胀的表上操作,慢查询开始冒头,恶性循环加速。

凌晨被叫第三次,我说:摘了

钉钉报警群连续十几条。P99 = 2300ms,全局锁死锁。三个月内第三次。

我拉着几个人语音,盯着监控红线看了二十秒。

我说:摘了吧。

没人说话。但也没人反对。

包括当初力推 Seata 的人——我自己。

替代方案简单到不好意思讲

本地消息表 + RocketMQ + 幂等消费。

Transactional Outbox Pattern,在同一个本地事务里写业务数据和消息,来源:microservicesio

核心思路:下单时在同一个本地事务里写两条数据——订单和一条「待发消息」。事务提交,两条必然同时存在;回滚,两条一起没。原子性由本地数据库保证,不需要第三方协调。

然后一个独立线程轮询消息表,投到 RocketMQ。下游幂等消费,失败重试,超限进死信,人工兜底。

就这样。没有 TC,没有全局锁,没有 undo_log。

上线一个月的数据:

指标 Seata 时期 本地消息表 变化
P99 640ms 68ms -89%
CPU 峰值 78% 34% -56%
undo_log 18GB 删了 -100%
线上事故 3 次/季度 0 清零

小陈看完方案说了一句:就这?

对,就这。

所以 Seata 到底什么时候该用

我不是说 Seata 是垃圾——它在特定场景下有价值:

场景 推荐方案 原因
QPS < 100,强一致 Seata AT 全局锁影响可忽略
金融核算,不容忍最终一致 Seata TCC 愿意付出开发复杂度换强一致
高并发,允许短暂不一致 本地消息表 + MQ 无全局锁,无单点
热点数据竞争 本地消息表 + MQ 全局锁必死
团队小、迭代快 本地消息表 + MQ 零运维负担

核心教训:强一致分布式事务很贵。没有框架能消灭这个成本,只能转移它。 Seata 转移到全局锁和 TC 上,你最终还是得买单。

而本地消息表 + MQ 的方案,代价是”最终一致”——数据在几百毫秒到几秒内达到一致。对绝大多数互联网业务来说,这个代价完全可以接受。

架构选型不是选”功能最强的”,是选”代价最小的”。 消息队列加本地消息表,代码简单,新人一天看懂,零运维依赖。这才是高并发场景下的正确答案。

文章目录
  1. 1. 先搞清楚 Seata 在背后干了什么
  2. 2. 500 QPS 就崩了,这性能你敢上生产?
  3. 3. 换 TCC?恭喜,代码量翻三倍
  4. 4. 核心链路上挂了个全局单点,谁给的勇气
  5. 5. DBA 甩了张截图:undo_log 18G
  6. 6. 凌晨被叫第三次,我说:摘了
  7. 7. 替代方案简单到不好意思讲
  8. 8. 所以 Seata 到底什么时候该用