这些年处理了不少微服务拆出来的“烂摊子”,核心感触就一句:很多人学分布式事务,学了一堆“招式”(TCC、Saga),却忘了最该练的“心法”:什么情况下,该扭头就走,根本不用出招。
这个心法,是我用真金白银换来的。曾有一个客户,他们的“订单-优惠券”链路强上了TCC,追求绝对强一致。结果大促夜里,一个网络分区导致Cancel请求乱序,不是空回滚就是悬挂,优惠券资源锁死,退款单乱飞。我们连夜回滚,切回“事务消息+定期对账”的老路,系统反而稳住了。那次之后我彻底明白,在分布式事务里选型,技术优劣是其次,代价与收益的算计才是根本。
下面这张图,是我这些年总结的决策框架,你可以把它看作面对这类问题时的“导航地图”:
导航图有了,我们再来细说每条路上的风景和坑。
踩坑实录:一致性之路,每一步都花钱
1. TCC的昂贵铠甲
上面说的优惠券案例只是冰山一角。TCC的Try-Confirm-Cancel模型,本质是让业务代码为分布式一致性买单。
- 坑1:空回滚与悬挂:这是分布式网络给你的“惊喜”。比如Try阶段超时,事务管理器触发Cancel,但这个Cancel可能比特慢的Try请求更早到达服务,导致“空回滚”;反之,Try在Cancel之后才到,事务就“悬挂”了。我们当时的解法是加一张分支事务状态表,先查后做。这方案能解决问题,但代价是复杂度飙升,数据库多了好几张表。
- 坑2:幂等性的沉重负担:Confirm/Cancel必须绝对幂等。这要求意味着任何外部调用(比如通知银行冲正)你都无法完全掌控。我们曾因一个非幂等的退款Cancel操作,在重试机制下给同一个订单退款三次,财务对账差点崩溃。
- 坑3:对业务的深度撕裂:一个简单的扣库存,你要拆出冻结(Try)、确认扣(Confirm)、解冻(Cancel)三个接口。业务逻辑被切得支离破碎,可读性和可维护性成倍下降。
我的看法:TCC方案,是给银行记账、核心库存变动这类“一分钱都不能错”的场景穿的铠甲,穿上它行动笨重、成本高昂;但如果你只是想发张积分券,这身铠甲就是累赘,能把自己压垮。
2. Saga的长路风险
后来做一个在线教育平台,订单涉及选课、排班、出资料,流程长,我们选了Saga。它的思想很直观:一个服务干完活发个事件驱动下一个,失败就发个补偿事件往回走。
- 看上去很美:服务间松耦合,加个新步骤(比如开电子发票)很方便。我们用状态机配置,感觉挺清晰。
- 现实的耳光:它的本质是最终一致。用户看到“报名成功”,可能排班正在失败,补偿操作还在路上。更麻烦的是“补偿链路的可靠性”:如果流程是A->B->C,C失败了,补偿顺序必须是C->B。要是B服务的补偿接口自己挂了,整个链路就“卡”在半路,留下一堆中间状态,最后基本靠人工查日志、写脚本去修补。
我的看法:Saga是处理跨越多服务、且业务上能接受短时间不一致的长流程的合适工具。但它把分布式事务的难题,从“原子提交”转换成了“补偿事务的可靠执行”。如果你没有强大的运维工具和人工兜底预案,这条路走起来会非常忐忑。
3. 可靠消息:老实人的方案
这是我最常推荐给普通业务的起点。核心就两步:1)业务操作和发消息放在同一个本地事务里;2)后台任务老实巴交地捞消息、发MQ、等确认。
- 优点:简单,可靠(消息不丢),对业务侵入相对可控。
- 最大的那个坑:消息可能重复。消费者必须自己做好幂等。我们吃过亏,一个消费者的幂等没做到位,配上MQ的“至少一次”投递,导致用户一晚上收到五条“发货通知”短信,客服电话被打爆。从此我们定下死规矩:所有消费消息的服务,接入时先过幂等设计评审。
4. 关于新工具:保持清醒
这几年Seata这类框架确实让接入变简单了,它的AT模式不用你写补偿逻辑,很诱人。但要知道,它通过拦截SQL生成回滚日志,把锁的粒度扩大了,在高并发场景对性能有可见影响,而且你整个系统的数据一致性,就押在它的TC(事务协调器)集群的高可用上。至于在Service Mesh层做事务,目前还太前沿,不稳定,看看就行。
商业化思考:这玩意儿如果当成服务卖,怎么算账?
如果我要把这套经验打包,卖给被同样问题困扰的公司,我的做法大概是这样:
1.售前诊断SOP(最重要的一步) :
-
- 先做减法:拿着最上面那张决策图,和对方的产品、技术、运营一起开会。核心就问一个事: “这个操作,用户如果等几分钟才知道最终结果,会不会投诉?会不会流失?” 如果答案是否定的,恭喜,方案成本和风险立减80%。
- 审计现状:看他们的服务拆得合不合理,是不是把本该在同一个数据库里完成的事,硬生生拆成了两个微服务来回调。
- 出具报告:明确划分,哪些链路是心脏,必须强一致(用TCC,报价高);哪些是四肢,可以接受最终一致(用可靠消息,报价中);哪些是头发,掉了无所谓(不处理,记录日志就行)。
2.风险与报价逻辑:
-
- 强一致方案(TCC/Seata AT) :按事务链路数量和峰值TPS报价。链路越多,要写的Try/Confirm/Cancel接口和状态维护就越多,是成本黑洞;TPS越高,对事务协调器的压力越大,运维保障成本指数级上升。这种项目,预算不上线我根本不敢接。
- 最终一致方案(消息队列) :按月消息吞吐量和数据修复时效要求报价。核心卖点不是“不出错”(一定会出错),而是“出了错能多快、多方便地被发现和修复”。我会把监控告警平台和可视化的数据核对修复后台作为核心交付物。这本质是卖“保险”和“售后服务”。
真正的交付物:
- 不是代码,而是一套《数据一致性兜底白皮书》和那个修复后台。让客户清清楚楚地知道,哪个环节可能出错、多久核对一次、出了问题按哪个按钮执行哪个修复脚本。向客户证明你有完整的“出事-发现-修复”能力,比假装系统永远不出事,要可信得多。
给读到这儿的不同朋友
- 给正在学习的朋友:别一头扎进TCC的代码里。先把数据库的本地事务、隔离级别,以及消息队列如何保证不丢、不重搞明白。这两样通了,你就有了一把尺子,能衡量所有分布式事务方案的代价。
- 给正在头疼的工程师/架构师:现在就按最前面那张图,盘一遍你的系统。我敢打赌,至少能找到三成所谓的“分布式事务”场景,可以通过调整业务逻辑、合并操作,或者就坦然地“记录日志,事后手工修复”来解决。高级的技术决策,往往包含着“不做”的勇气。
- 给技术负责人或老板:让你的团队算笔账:为了追求一个理论上的“数据完美”,你们在开发、测试、运维上投入了多少人力和时间成本?再算算,如果一个月发生几次数据不同步,人工排查修复的成本是多少。十之八九,后者的数字小得多。 把省下的资源,投入到提升真正的系统稳定性和用户体验上,更务实。
分布式事务从来不是一个单纯的技术选择题,它是一个工程经济题:在正确的业务场景下,选择综合代价最低的技术路径,并为这个选择准备好坚实的退路。
你遇到过最棘手的数据一致性问题是什么?最后是怎么解决的?欢迎在评论区聊聊,我们一起把这事弄得更明白些。