分布式事务解决方案:一次真实项目中的战斗
开头:为什么我决定写这篇文章
最近在公司的一个新项目中,我又一次撞上了那个“老朋友”——分布式事务问题。这次我们做的是一个跨多个服务的订单履约系统,用户下单后需要调用库存服务、积分服务、物流服务等多个下游系统,还要保证状态的一致性。
刚开始开发的时候大家都没太当回事,觉得加个事务不就完了?但当你面对多个微服务、多库、甚至多个业务单元时,事情就没那么简单了。这篇文章我想用自己的亲身经历,讲讲我们在实际项目中遇到的问题、踩过的坑,以及最终是如何找到一套“能打”的解决方案的。
项目背景与挑战描述
我们的项目是给某大型电商平台新增一套订单履约系统,目标是支持复杂的履约逻辑,包括:
- 订单创建
- 库存扣减
- 积分扣除
- 物流单推送
- 状态更新和消息通知
整个系统采用的是典型的微服务架构,拆分为订单服务、库存服务、积分服务、物流服务等几个核心模块,各自拥有独立数据库和接口。
初期设计(简单粗暴)
最开始的设计是这样的:
- 下单完成后,通过同步 RPC 调用各个服务,执行各自的写操作;
- 所有服务都返回成功才算整个流程完成;
- 如果中间任何一个失败,立即回滚当前流程。
看起来没毛病对吧?但我们很快遇到了几个关键问题:
- 接口超时导致整体流程卡死;
- 部分服务失败无法回滚,比如已经扣了积分,但库存扣减失败;
- 数据一致性难以保障;
- 重试机制复杂且容易重复扣款/发货;
更严重的是,有一次灰度发布时,因为一个服务异常未返回正确响应码,导致一批用户的积分被重复扣除,直接上生产故障单。
这下大家都意识到:我们必须为这个系统引入一套可靠的分布式事务机制了。
解决方案探索与选型
针对这个问题,我们先后考虑过几种主流方案:
方案一:两阶段提交(2PC)
2PC 是一种强一致性协议,适合强一致场景,但在实践中存在明显缺点:
- 同步阻塞时间长;
- 协调者节点存在单点风险;
- 网络不稳定时易造成数据不一致;
- 微服务之间接口通常没有 XA 支持。
我们调研了一下目前的服务现状,几乎不具备支持 2PC 的条件,而且对性能要求高,不能接受长时间阻塞。
结论:PASS
方案二:TCC(Try-Confirm-Cancel)
这是我们最终选择的主要方案之一。
TCC 的思想是:将一个操作分为三个阶段:
Try阶段:资源预留(例如冻结库存、冻结积分);Confirm阶段:业务执行(如正式扣减库存);Cancel阶段:回滚处理(释放预留资源);
这种方式的好处在于不需要数据库的支持,只需要服务本身实现这三个接口即可,非常适合我们的业务场景。
我们是怎么做的?
以“下单+扣库存+扣积分”为例:
// 在订单服务中
public Order placeOrder(...) {
// Step 1: Try phase
inventoryService.prepareInventory(orderId, items);
pointsService.deductPointsPreparation(userId, points);
// Step 2: 创建订单
Order order = createOrderInDB(...);
try {
// Step 3: Confirm phase
inventoryService.confirmInventoryDeduction(orderId);
pointsService.confirmPointsDeduction(userId);
} catch (Exception e) {
// Step 4: Cancel phase
inventoryService.rollbackInventory(orderId);
pointsService.rollbackPoints(userId);
throw new OrderProcessingException("Failed to confirm deduction", e);
}

return order;
}
当然,这只是伪代码。真实实现远比这个要复杂得多,尤其是关于幂等、补偿逻辑、日志追踪这些方面。
补充说明:幂等控制很重要!
我们在每个接口中都加入了全局唯一的业务流水号(bizId)和请求ID(requestId),确保即使网络抖动或重发也不会造成重复操作。
实施难点
- 每个服务都要自己维护 TCC 接口;
- 异常场景下的状态追踪非常麻烦,需要引入事务日志;
- 补偿过程必须自动、可靠,否则就会出现“卡单”问题;
- 对业务逻辑的理解成本较高,需要团队统一认知;
为了简化管理,我们还专门搭建了一个事务协调服务(Transaction Coordinator),负责记录每一步的状态、发起补偿、触发异步清理任务等。
方案三:SAGA 模式
SAGA 是另一种常用的分布式事务模式,特点是每个子事务独立提交,如果失败则依次执行逆向补偿动作。
我们也做过尝试,但在我们这种对一致性要求极高的电商场景下,SAGA 太容易出错,补偿链太长,很难跟踪。所以最后还是选择了相对更可控的 TCC。
方案四:本地事务表 + MQ 异步补偿
这个思路我们主要用来处理订单状态同步、短信/邮件通知等非核心路径的环节。
具体做法是:
- 在主业务库中记录本地事务表(local_transaction);
- 使用消息队列(Kafka / RocketMQ)发送异步事件;
- 消费端监听事件,并消费成功后标记事务完成;
- 定时任务扫描未完成事务并重试。
优点是性能好、解耦程度高、适用于非实时性强一致场景。
实践成果与效果评估
这套基于 TCC + 异步补偿组合的方案实施之后,我们系统的稳定性和一致性得到了显著提升。
关键指标变化如下:
| 指标 | 上线前 | 上线后 |
|---|---|---|
| 平均下单耗时 | 800ms | 500ms |
| 数据不一致率 | ~3% | <0.1% |
| 日均故障单数量 | 10+ | 0~2 |
| 接口成功率 | ~97% | >99.8% |
最重要的是,在后来的几次大促中,系统都能扛住流量压力,没有再发生积分误扣、库存负值等事故。
虽然开发成本高了一些,但从稳定性角度看是非常值得的。
经验总结与建议
如果你也在做类似的微服务项目,以下是我亲身踩坑后的一些经验分享:
1. 不要低估一致性的重要性
尤其是在资金、库存类操作中,任何小的失误都会造成巨大影响。哪怕你只漏掉了一种异常情况,可能就需要人工介入修复数据。
2. TCC 是把双刃剑,用不好反伤己身
TCC 带来的最大好处就是灵活性,但它也对代码质量、接口幂等、事务追踪提出了更高要求。如果没有完整的事务日志体系,很容易陷入“卡单”的黑洞。
3. 日志才是救命稻草
我们专门为每个事务流程设计了一个“事务上下文日志”,记录每一步操作的输入输出、耗时、状态、是否重试、重试次数等信息。这些数据不仅对排查问题非常重要,后期还可以用来做数据分析和监控报警。
4. 异步补偿也要设计成可追踪、可恢复
我们曾经犯过一个错误:把订单状态同步完全交给 Kafka 消费,结果 Kafka 出现短暂不可用,导致很多订单一直处于“已创建未发货”状态,客户投诉激增。
后来我们加上了事务状态检查 + 死信队列 + 定时扫表补发,才解决了这类问题。
5. 做好技术降级预案
比如:在某个服务不可用时,应该允许跳过非核心事务步骤,但要明确标注“待后续补偿”。
当下趋势与未来展望
目前业界对于分布式事务的关注点正在往两个方向走:
- 柔性一致性 + 异步补偿:这也是我们方案的核心思想之一;
- 基于 Event Sourcing 和 CQRS 架构的新玩法:虽然暂时还没用到,但已经在我们架构升级计划中。
另外,像 Seata、Apache ServiceComb Saga 这些分布式事务框架也在不断完善中,未来我们可以尝试集成使用,减少一些自研成本。
写在最后:开发不是搭积木,而是在拼图
说实话,写这篇文章的时候,我还是有点感慨。
刚接触分布式事务的时候,我也觉得只要加个注解就能搞定一切;但真正落地的时候才发现,它背后牵涉的不只是技术,还有产品逻辑、运维策略、运营配合,甚至是公司流程制度。
每一次系统重构,都是一次重新认识自己的机会。而每解决一个问题,也都意味着我们离“高可用、高性能、高扩展”的目标又近了一步。
希望这篇来自实战的文章,能帮助你少走弯路。如果你也在处理类似的问题,欢迎留言交流,我们一起成长!
作者简介:
一名工作6年的后端开发者,经历过从单体架构到微服务再到云原生的全过程。目前专注系统稳定性、高并发场景优化和分布式系统设计。热爱开源,喜欢捣鼓各种中间件源码。

评论 0