请写一篇关于【分布式事务解决方案:最佳实践】的技术文章
开篇:一碗云吞面,一个凌晨三点的崩溃
去年十月的一个深夜,广州老城区下着淅淅沥沥的小雨。我坐在西关老屋的窗边,桌上摆着一碗早已凉透的云吞面——那是老婆小敏临睡前给我留的宵夜。屏幕上的日志疯狂滚动,红色的 Transaction Rollback Failed 像血一样刺眼。
“又来了……”我揉了揉酸胀的眼睛,心里一阵发苦。
那会儿我刚接了一个远程外包项目,客户是国内一家做供应链金融的 SaaS 产品公司。他们用 Java 写的微服务架构,订单、库存、支付、账务四个核心模块各自独立部署,跨服务调用全靠 REST API。听起来挺高大上,但问题也来了:分布式事务。
简单说就是,用户下单付钱,系统要同时扣库存、生成订单、记录账务。任何一个环节失败,其他操作都得回滚。可现实是,库存服务挂了,订单却创建成功了;或者支付回调超时,账务没记上,钱却扣了……用户投诉像雪片一样飞来。
老板在群里@我:“阿强,这问题再不解决,客户就要砍预算了。”
我回了个“收到”,心里却像被塞了块冰——月薪从15k涨到22k的承诺,眼看就要泡汤。
我是谁?一个在广州老城区敲代码的自由开发者
先自我介绍一下吧。我叫阿强,土生土长的广州人,住在荔湾老城区一栋80年代的楼梯楼里,月租3500,走路五分钟到泮塘五约。大学学的是计算机,毕业后在天河科技园干了五年Java后端,前年疫情一来,公司裁员,我干脆咬牙辞职,成了自由开发者。
说实话,刚开始还挺爽:不用挤地铁,不用开站会,睡到自然醒,下午还能陪老妈去菜市场买鲩鱼。但自由的代价是——没人兜底。项目出了问题,你就是最后一道防线。
这次的分布式事务问题,就是我转型路上最痛的一课。
初期尝试:天真地以为加个 @Transactional 就行了
接到项目时,我心里其实有点飘。毕竟写了七八年 Java,Spring Boot、MyBatis、Redis 这些工具玩得挺溜。看到需求文档里写着“保证数据一致性”,我还跟小敏开玩笑:“不就是加个 @Transactional 吗?小学生都会。”
结果现实狠狠打了我一巴掌。
我一开始用了最简单的 本地事务 + 重试机制:每个服务内部用 Spring 的 @Transactional 保证自己的数据库操作原子性,失败就重试三次。但问题在于——跨服务调用不是原子的。
比如:
- 订单服务创建订单(本地事务成功)
- 调用库存服务扣库存(网络抖动,超时)
- 系统认为失败,订单回滚
- 但库存服务其实在超时后处理成功了……
于是出现了“订单没生成,库存却少了”的鬼畜场面。
更糟的是,有一次支付回调延迟,账务服务没收到通知,用户钱扣了但没入账。客户直接打电话骂到项目经理头上,项目经理转头就把我微信拉黑了三天。
那几天我焦虑得睡不着,半夜爬起来查资料,头发一把一把掉。小敏看不下去,说:“要不咱接点简单的外包?别搞这些高大上的玩意儿了。”
我苦笑:“可这单做完,咱就能换套带电梯的房子了啊……”
转机:从“轮子哥”那儿偷来的思路
转机出现在一个技术沙龙。地点在珠江新城某共享办公空间,门票88块,我犹豫半天还是去了——因为主讲人是业内有名的“轮子哥”,开源过好几个 Java 分布式框架。
他讲到一半,突然说:“很多人以为分布式事务就是技术问题,其实是产品设计问题。”
这句话像闪电劈进我脑子。
他举了个例子:与其追求“强一致性”,不如在产品层面接受“最终一致性”,并通过状态机 + 补偿机制来兜底。比如电商下单,可以先冻结库存,等支付成功再真正扣减;如果支付失败,自动释放冻结。
我当场掏出手机狂记笔记,连茶歇的蛋糕都没顾上吃。
回来后,我重新梳理了整个业务流程,和产品经理开了三次线上会议(他人在杭州,说话带浓重口音,但逻辑极清晰)。我们达成共识:不要追求实时一致,而是通过状态流转 + 幂等 + 补偿,把问题拆解成可管理的单元。
实战:三种方案的落地与踩坑
方案一:TCC(Try-Confirm-Cancel)——优雅但复杂
TCC 是我最先尝试的。原理很简单:
- Try 阶段:预留资源(如冻结库存)
- Confirm 阶段:真正执行(扣减库存)
- Cancel 阶段:释放资源(解冻)
我用 Java 写了三个接口,每个服务都要实现 TCC 三态。工具上选了 Seata ——阿里开源的分布式事务框架,对 Spring Cloud 支持不错。
但问题很快来了:开发成本太高。每个业务方法都要拆成三份,测试用例翻三倍。更坑的是,一旦 Confirm 阶段失败,Cancel 可能也无法执行(比如数据库连接池爆了),导致脏数据。
上线一周后,因为一个边界 case 没覆盖,库存被重复冻结。客户差点报警。
我蹲在阳台抽烟时想:“这玩意儿是不是只适合大厂?我们这种小团队玩不起啊……”
方案二:消息队列 + 本地消息表 —— 土但稳
痛定思痛,我转向了更接地气的方案:基于可靠消息的最终一致性。
具体做法:
- 订单服务在本地事务中,同时写订单表 + 消息表(状态为“待发送”)
- 后台定时任务扫描消息表,发 MQ(RabbitMQ)
- 库存服务消费消息,执行扣库存,成功则回写 ACK
- 如果失败,消息重试,直到成功或人工介入
这个方案的好处是:只依赖本地事务 + 消息队列,不需要额外协调器。Java 里用 Spring 的 @Transactional + RabbitTemplate 就能搞定。
我花了三天重构代码,把原来的 REST 调用全换成 MQ 异步通知。虽然延迟从 200ms 升到 1s+,但稳定性飙升。
最关键的是——产品侧配合做了优化:前端下单后显示“处理中”,1秒后自动刷新状态。用户根本感知不到背后的复杂性。
上线两周,0 数据不一致。客户主动加了5k尾款。
方案三:Saga 模式 —— 长事务的救星
后来项目扩展到跨境支付场景,涉及5个以上服务串联,TCC 太重,消息表又难以追踪全链路。
这时候我祭出了 Saga 模式:把整个流程拆成多个本地事务,每个步骤配一个补偿操作(Compensate)。如果第 N 步失败,就逆序执行 N-1, N-2… 的补偿。
工具上我用了 Eventuate Tram(一个支持 Saga 的 Java 框架),但配置复杂到想哭。最后干脆自己写了个简易版:用状态机引擎 + 补偿任务队列。
举个例子:
- Step1: 创建订单 → Compensate: 删除订单
- Step2: 扣库存 → Compensate: 加回库存
- Step3: 调用支付网关 → Compensate: 发起退款
虽然补偿逻辑要写两遍,但业务语义清晰,产品经理都能看懂流程图。
区块链?别被 buzzword 带偏了
说到这儿,可能有人要问:“你提到了区块链,它能不能解决分布式事务?”
老实说,我研究过。Hyperledger Fabric、Ethereum 的智能合约确实能提供不可篡改的交易记录,但——性能太低,成本太高。
我们的系统日均订单5万+,用区块链?Gas 费能把客户吓跑。而且区块链解决的是“信任”问题,不是“一致性”问题。我们内部服务之间本来就是可信的,何必上链?
所以我的结论是:除非你的产品本身就是去中心化应用(DApp),否则别为了蹭热点硬上区块链。工具要用在刀刃上,不是贴金箔。
工具链:我的 Java 分布式事务武器库
这两年踩坑下来,我总结了一套轻量级工具组合:
- Seata:适合强一致性场景,但慎用 AT 模式(性能差),优先考虑 TCC。
- RabbitMQ / Kafka:可靠消息投递的基石,记得开启持久化 + 手动 ACK。
- ShedLock:分布式定时任务锁,防止补偿任务重复执行。
- Prometheus + Grafana:监控事务成功率、延迟、重试次数。
- 自研状态机引擎:用 Enum + Strategy 模式管理业务状态流转。
所有代码都用 Spring Boot 2.7 + Java 17 编写,Lombok 减少样板代码,MapStruct 做 DTO 转换——效率提升30%不止。
感悟:技术之外,是人与产品的共舞
现在回头看,分布式事务从来不只是技术问题。
它逼我走出“纯技术思维”:不再执着于 CAP 定理谁对谁错,而是问——用户能容忍什么?产品愿意牺牲什么?
有一次和产品经理争论“是否允许短暂超卖”,我说:“技术上可以做到零超卖。”
他反问:“但如果因此导致下单失败率上升5%,损失的是真金白银。你愿意背这个锅吗?”
我哑口无言。
那一刻我明白了:好的架构,是技术和商业的妥协艺术。
给同行的建议:别怕“土办法”
如果你也在折腾分布式事务,我的真心话是:
- 先问产品目标:是要强一致,还是最终一致?别一上来就谈 Paxos。
- 从小处着手:消息表 + 本地事务 能解决80%的问题,别急着上 Seata。
- 幂等是生命线:每个接口必须支持重复调用,否则补偿机制会崩。
- 监控比代码重要:没有可观测性,你就是在黑暗中裸奔。
- 别迷信新技术:区块链、Serverless 很酷,但不一定适合你的场景。
尾声:窗外的木棉花开了
写完这篇稿子时,已是三月。窗外的木棉花开得正盛,红得像火。小敏在厨房煲老火汤,香味飘进来,混着键盘的敲击声,竟有种奇异的踏实感。
自由开发者这条路,走得磕磕绊绊。有过凌晨三点的崩溃,也有过收到尾款时的狂喜。但正是这些“坑”,让我从一个只会写 CRUD 的码农,慢慢长成了能扛事的工程师。
分布式事务没有银弹,人生也没有。我们能做的,是在约束中寻找最优解,在混乱中建立秩序——无论是代码,还是生活。
最后送大家一句我贴在显示器边上的话:
“Consistency is overrated. Correctness with recovery is underrated.”
共勉。
—— 阿强,于广州西关老屋
2024年3月18日

评论 0