分布式事务解决方案:最佳实践(一个被房贷和地铁压垮的北漂程序员的血泪总结)

知识库管理员
2025-12-14 22:43
阅读 593

上周五晚上 10 点半,我拖着灌了铅的双腿从世纪大道地铁站走出来,手机震动了一下——是女朋友发来的消息:“饭热好了,你到哪了?”
我看了眼导航,离家还有 800 米。但此刻脑子里全是白天那个线上事务不一致的告警:用户付了钱,订单状态却还是“待支付”。老板在群里@我:“这都第几次了?再出问题,Q3 绩效直接打 C。”

我站在路口喘了口气,心里骂了一句:“分布式事务,你他妈能不能别再折磨我了?”


我是谁?一个被现实按在地上摩擦的 Java 程序员

先自我介绍一下吧。我是老张,坐标上海浦东,和女朋友合租在金桥的一个老小区,月租 3500(押一付三,每次交租前一周就开始焦虑)。每天早上 7:15 出门,挤 2 号线转 9 号线,单程 1 小时 10 分钟;晚上回来基本 9 点以后。月薪从 15k 涨到 22k 那天,我还请女朋友吃了顿人均 200 的日料——结果第二天就收到银行短信:“本月房贷还款 6843.21 元已扣款。”

我们做的系统是个电商中台,订单、库存、账户分三个微服务,全用 Spring Boot + MySQL 写的。去年十月上线后,事务问题就没断过。老板说:“你们搞技术的,不就是解决这种问题的吗?” 我心想:说得轻巧,你来写两行 TCC 试试?


被逼上梁山:从“我以为”到“我裂开了”

一开始,我和组里另一个兄弟小李天真地以为,加个 @Transactional 就万事大吉了。毕竟在学校、培训班、甚至 GitHub 上看的 demo,都是单体应用,事务回滚顺滑如德芙。

结果上线第一天,用户下单成功,库存没扣,钱却扣了。客服电话被打爆。CTO 在会议室拍桌子:“你们是不是没学过分布式事务?!”

我弱弱地说:“学过……但课本上没教怎么扛住 5000 TPS 啊。”

于是,我们被迫开始调研真正的分布式事务方案。以下是我踩过的坑和吐过的槽。


方案一:2PC(两阶段提交)—— 理论很美,现实很骨感

2PC 是教科书级方案,XA 协议那一套。MySQL 从 5.7 开始支持 XA 事务,理论上能保证强一致性。

实测结果?

  • 吞吐量直接掉到 300 TPS,比单机还慢。
  • 任何一个节点挂了,整个事务卡住,资源锁死。
  • 运维大哥看到监控图直接冲进我们工位:“你们谁写的代码?数据库连接池快爆了!”

结论: 别碰。除非你公司有钱到能养一支 DBA 团队专门调优,或者业务量小到一天就几十单——那你还搞啥微服务?


方案二:TCC(Try-Confirm-Cancel)—— 强一致性,但代价巨大

TCC 是阿里开源 Seata 主推的模式。核心思想是:把业务逻辑拆成三步——先冻结资源(Try),再确认(Confirm),失败就取消(Cancel)。

听起来很牛,对吧?但落地的时候我差点辞职。

比如“扣库存”这个操作:

  • Try:库存 -1,但状态设为“预占”
  • Confirm:正式扣减
  • Cancel:释放预占

问题来了:每个接口都要写三套逻辑! 而且要处理幂等、空回滚、悬挂等问题。我们光是“订单创建”一个流程,就写了 300 多行补偿代码。

更惨的是,有一次网络抖动,Confirm 请求丢了,系统以为失败了,其实库存已经扣了。用户投诉:“我付了两次钱,只收到一个货!”

GitHub 上搜 TCC,Star 最多的项目 issue 区里全是:“Cancel 没触发怎么办?”、“如何保证 Try 和 Confirm 的原子性?”

我的感受: TCC 能用,但成本太高。适合金融级场景,比如转账。但我们这种卖日用品的电商?ROI(投入产出比)太低。


方案三:本地消息表 + 最终一致性 —— 打工人之光

被 TCC 折磨一个月后,我在 GitHub 上翻到了一个叫 eventuate-tram 的项目(虽然现在不太维护了),灵感来自《微服务架构设计模式》这本书。

思路很简单:

  1. 在本地事务中,同时写业务数据 + 发送消息记录(比如“订单已创建”)
  2. 用定时任务或消息队列(比如 RabbitMQ/Kafka)异步消费这条消息,去调库存服务
  3. 如果失败,重试 N 次,直到成功

优点?

  • 不依赖外部协调者,性能好(我们压测到 4000+ TPS)
  • 代码侵入小,只需要加个消息表
  • 失败可追溯,重试机制清晰

缺点?

  • 数据不是实时一致,有延迟(用户可能看到“订单成功”但库存还没扣,几秒后才同步)
  • 要自己处理消息重复(幂等!幂等!幂等!重要的事说三遍)

但对我们来说,最终一致性完全够用。用户又不是做高频交易,等 2 秒看到库存更新,根本无感。


方案四:Seata AT 模式 —— 阿里爸爸的“银弹”?

去年底,公司决定上 Seata。AT 模式号称“零代码改造”,自动解析 SQL 生成 undo log,像本地事务一样用。

我兴奋地熬了两个通宵集成,结果……

  • Undo log 表膨胀得飞快,DBA 喊停
  • 跨库查询直接报错
  • 一旦主键不是自增 ID,回滚就乱套

最离谱的是,Seata Server 本身成了单点故障。有次它挂了,整个下单链路瘫痪 40 分钟。

GitHub 上 Seata 的 issue 区堪称大型吐槽现场:“AT 模式在生产环境真的能用吗?”、“undo_log 表怎么清理?”、“为什么回滚后数据还是脏的?”

我的结论: Seata 适合新项目、规范统一的团队。但我们这种历史包袱重、数据库设计五花八门的老系统?别硬上,容易翻车。


最终选择:本地消息表 + RocketMQ 事务消息

折腾半年后,我们定了最终方案:

  • 核心链路(下单、支付)用 RocketMQ 事务消息
  • 非核心(发券、积分)用 本地消息表 + 定时补偿

RocketMQ 的事务消息机制很巧妙:

  1. 先发 Half Message(半消息)
  2. 执行本地事务(比如扣账户余额)
  3. 成功则 Commit,失败则 Rollback

它内部用 checkpoint 机制保证消息不丢,而且支持 Exactly-Once 语义。虽然要写点回调逻辑,但比 TCC 简单多了。

关键是:稳定! 上线三个月,0 事务不一致事故。老板终于不再 @ 我了。


写给和我一样的普通程序员

我知道,很多人看到“分布式事务”就头大。网上教程动不动就“CAP 定理”、“BASE 理论”,搞得好像不用 TCC 或 Saga 就不配当架构师。

但现实是:大多数业务,根本不需要强一致性。

你月薪 22k,背 6800 的房贷,挤两小时地铁,不是为了实现学术理想,而是让系统稳稳跑起来,让用户别投诉,让自己能准点下班陪女朋友吃顿热饭。

所以我的建议很朴素:

  1. 先问业务容忍度:能接受几秒延迟?能接受少量人工干预吗?
  2. 优先选最终一致性:本地消息表 or 事务消息,简单可靠
  3. 别迷信大厂方案:阿里的 TCC 是为双 11 设计的,你可能连 11 都没有
  4. 善用 GitHub:别重复造轮子,但一定要读源码、看 issue,别光看 README

结尾:技术之外,生活才是终极事务

昨天晚上,女朋友看我还在改代码,叹了口气:“你什么时候能不加班啊?”
我说:“等我把这个分布式事务搞定。”
她笑了:“那你这辈子都别想准时回家了。”

我愣了一下,也笑了。

其实,人生何尝不是一场“最终一致性”的事务?
工作、爱情、房贷、梦想……它们永远不会在同一刻完美同步。
但只要不断重试、补偿、向前推进,终会达到一种动态的平衡。

而我们这些背着房贷的北漂程序员,不就是在这样的不一致中,努力寻找属于自己的“Commit”时刻吗?

P.S. 本文所有方案代码我都整理到了 GitHub:https://github.com/zhang-bj/distributed-tx-practice(别 star,没人维护,纯个人笔记)
如果你也正在被分布式事务折磨,欢迎留言。至少,我们还能互相骂一句:“这破系统,真难搞!”

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝