一个后端开发者的实战经验:分布式事务的那些事儿
引言:为什么会谈到分布式事务?
在我五年的后端开发工作中,最常让我“睡不着觉”的问题之一,就是分布式事务。在早期的单体架构下,我们习惯了数据库的本地事务来保证数据一致性,但随着业务发展和系统规模扩大,微服务成了主流架构风格。
当多个服务之间需要协同完成一个操作时,比如下单支付、库存扣减、积分增加等,这些看似顺理成章的业务动作,一旦涉及到多个独立系统的数据修改,就变成了“要么都成功,要么全失败”的分布式事务场景。
这篇文章我打算以一次真实的项目经历为主线,讲讲我是怎么一步步解决分布式事务难题的。过程中踩过坑,也收获了不少宝贵的经验,希望我的分享能对正在面临同样问题的你有所帮助。
一、背景介绍:一个电商平台的订单模块重构

1.1 项目背景
那是在2021年,我参与了一个电商平台的订单中心重构项目。原来的系统是单体应用,随着用户量的增长,性能瓶颈日益明显,于是决定进行服务化改造,将原有的订单模块拆分为:
- 订单服务(Order Service):负责创建、修改订单信息
- 支付服务(Payment Service):处理付款相关逻辑
- 库存服务(Inventory Service):管理商品库存数量
- 积分服务(Point Service):记录用户积分变化
这四个服务彼此通过 RESTful API 或者 gRPC 相互通信。整个链路大概是这样:
用户提交订单 → 扣减库存 → 创建订单 → 发起支付 → 增加积分
这是一个典型的业务闭环,而且各服务的数据必须保持一致性。只要其中一个步骤出错,整个流程都应该回退。
但在微服务架构下,传统的数据库事务已经无能为力了。我们需要一种跨服务的事务协调机制,也就是——分布式事务。
二、遇到的问题:系统稳定性差,数据不一致频发

2.1 初期方案:本地事务 + 消息补偿
刚开始我们尝试的是一个很常见的做法:在主服务中先做本地事务,再调用其他服务,并通过 MQ 进行异步补偿。
举个例子,在订单服务中,我们这样设计流程:
- 在订单表中插入一条状态为“待支付”的订单;
- 调用库存服务扣减库存;
- 如果失败,则发送一条消息到 RabbitMQ,后续由定时任务重试;
- 后续支付完成后,再调用积分服务增加用户积分,同样也是异步处理。
听起来挺合理的,对吧?但在实际运行中,我们遇到了几个大问题:
- 超时与幂等性控制难:由于网络波动或服务响应慢,导致多次请求,最终出现重复扣库甚至多加积分;
- 补偿机制复杂且易出错:每个服务都要维护补偿逻辑,还要自己写定时任务、记录日志,维护成本高;
- 数据一致性难以保障:比如支付成功了但积分没加,或者订单已创建但库存没扣,最终导致用户投诉。
我们收到了很多运营反馈:“为什么我付了钱,库存却没扣?”、“为什么用了优惠券,积分也没增加?”
那一刻我意识到,这套“土办法”撑不了多久。
三、技术选型与方案演进

既然传统手段行不通,那我们就得认真考虑更成熟的分布式事务解决方案了。接下来我们调研了几种主流方案,最终选择了“TCC + 最终一致性”的混合模式。
3.1 TCC 是什么?
TCC 是一种两阶段提交协议的变种,全称是 Try - Confirm - Cancel:
- Try 阶段:资源预留(冻结金额、预扣库存)
- Confirm 阶段:业务执行(正式扣库存、确认订单)
- Cancel 阶段:取消预留(解冻金额、恢复库存)
这个模式的核心思想是:每个服务自己实现这三个接口,由事务协调器(Coordinator)统一调用。
3.2 我们的实现思路
我们在订单中心引入了一个轻量级的事务协调组件,核心功能包括:
- 生成全局事务 ID(XID)
- 记录事务日志
- 管理事务生命周期(开始、提交、回滚)
- 出现异常时自动触发 Cancel 补偿机制
示例:TCC 接口定义(伪代码)
public interface InventoryService {
void tryDecreaseStock(String productId, int quantity); // 冻结库存
void confirmDecreaseStock(); // 正式扣减
void cancelDecreaseStock(); // 回滚库存
}
具体流程如下:
- 用户下单后,订单服务开始一个 TCC 事务,生成 XID;
- 调用库存服务
tryDecreaseStock()接口冻结库存; - 创建订单并写入数据库;
- 调用支付服务冻结用户账户余额;
- 支付成功后,分别调用各服务的
confirmXXX()提交事务; - 若其中某一步失败,则调用所有服务的
cancelXXX()回滚事务。
当然,这里面还有很多细节要做,比如幂等性判断、重试机制、日志追踪等等。
四、落地过程中的挑战与优化
虽然 TCC 的理论看起来挺清晰,但在实际落地过程中,还是踩了很多坑。
4.1 幂等性是个大坑
由于服务间调用可能会出现超时重试的情况,如果不对 Try / Confirm / Cancel 接口做幂等性控制,很容易出现重复操作,导致库存多扣或积分多加。
我们采取的做法是:
- 在事务协调器中维护每一步的操作是否已经执行;
- 在每个服务内部使用“事务ID+操作ID”的组合键做防重判断;
- 数据库层面配合 Redis 缓存去重。
这大大提升了系统的健壮性。
4.2 日志追踪与调试困难
一开始我们的事务协调组件没有完善的日志系统,一旦出错只能靠人工排查,效率极低。
后来我们接入了 SkyWalking 分布式追踪系统,结合事务 XID 实现了全流程的日志追踪:
- 每次事务操作都能打上对应的 XID;
- 所有服务的日志都可以通过 TraceID 快速查找;
- 出现故障时可以快速定位是哪个环节出了问题。
这个小改动让运维效率提升了不少。
4.3 性能瓶颈初现
随着系统上线初期流量上涨,我们发现事务协调组件本身成为了瓶颈。尤其是在高峰期,大量并发请求进来,协调器处理不过来。
怎么办?我们进行了以下优化:
- 异步落盘日志:不再同步写事务日志到数据库,改为写入本地文件 + 定时刷盘;
- 连接池复用:各个服务之间的 RPC 调用使用连接池复用机制,减少握手开销;
- 限流降级:对关键接口如库存扣减设置了熔断策略,防止雪崩。
这些措施实施之后,系统的吞吐能力有了明显提升。
五、最终效果与收益
经过几个月的打磨和线上验证,我们的分布式事务系统终于趋于稳定。主要效果如下:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 事务成功率 | 约 92% | >99.8% |
| 平均事务耗时 | ~800ms | ~200ms |
| 错误率(数据不一致) | 0.5% | <0.01% |
| 维护成本 | 高 | 中等 |
更重要的是,用户投诉少了,系统更加健壮了,我们也更有信心面对业务高峰。
六、给开发者的一些建议
如果你现在也在纠结分布式事务这个问题,不妨听听我作为过来人的一些想法:
6.1 不要一开始就追求“完美方案”
很多人一上来就想上分布式事务框架,比如 Seata、Hmily 等等,其实不是非得这样。根据业务场景选择合适的技术才是关键。
如果是数据一致性要求不高,容忍短时间不一致,可以采用基于消息队列的最终一致性,简单高效;
如果对一致性要求极高,那么 TCC 或 Saga 模式更适合你。
6.2 技术方案背后是工程能力和团队协作
分布式事务不仅仅是技术问题,更是工程实践的挑战:
- 你需要统一接口规范;
- 要求每个服务具备良好的幂等性和补偿机制;
- 需要完善的监控、日志、链路追踪体系;
- 运维同学也要熟悉如何排查和恢复事务。
所以,别指望靠一个组件就能解决问题,它需要整个团队的共识和配合。
6.3 多看生产环境日志,少依赖测试用例
分布式事务的一个难点在于,它的不可控因素太多:网络延迟、服务宕机、数据脏乱……
测试环境永远模拟不到真实情况。我们曾经在一个版本上线后,因为某个服务在 Confirm 阶段返回了一个空指针错误,导致事务无法提交。
这类问题只有在线上运行一段时间后才会暴露出来。所以一定要做好灰度发布、自动化报警和应急预案。
七、未来趋势与个人思考

随着云原生的发展,越来越多的基础设施也开始支持事务管理。比如:
- Kubernetes + Istio 服务网格:提供了更好的流量治理能力;
- Serverless 架构:部分函数即服务(FaaS)平台已经开始内置轻量级事务能力;
- 云厂商的分布式事务中间件:阿里云 DTX、AWS SAGA 框架等,逐步走向成熟。
但我认为短期内,本地化可控的事务协调机制仍然不可或缺。毕竟不是每个团队都能完全跑在云厂商的生态里,也不是每个业务场景都适合 Serverless。
对于中小型团队来说,实现一套可扩展、可维护的 TCC 框架 + 最终一致性补偿机制仍然是性价比最高的方式。
结语:分布式事务,不只是技术问题
最后我想说的是,分布式事务是一个技术、工程、运维、产品多方交织的问题。
作为一个后端工程师,我们要做的不仅仅是写好接口,更要从整体角度去思考系统的设计、架构的演变以及用户体验的保障。
回顾这段旅程,虽然痛苦,但正是这些挑战让我成长为一个更加全面的工程师。
如果你也在分布式事务的路上,请记住一句话:不要怕慢,只怕停。
共勉!
作者简介:五年后端开发经验,经历过大型电商系统重构、金融风控系统搭建,关注分布式系统设计与落地实践。欢迎交流!

评论 0