分布式事务解决方案:我的真实实战经验分享
作为一名有5年后端开发经验的老兵,我在多个大型系统项目中都遇到了一个几乎绕不开的难题:如何正确地处理分布式事务?
这篇文章不是为了复述理论,而是想用我在实际工作中踩过坑、熬过的夜、以及最终找到的一条相对靠谱的路,来跟大家聊聊“分布式事务”到底怎么玩才稳。
背景介绍:为什么需要关心分布式事务?

我们先从一个典型的业务场景说起。去年我参与了一个电商平台重构项目,系统拆分成了多个微服务,比如订单中心、库存中心、用户中心、支付中心等。各个服务之间通过 RPC 或 REST 接口通信。
这时候就出现了一个经典的问题:
当用户下单时,要同时完成以下操作:
- 下单成功(写入订单表)
- 扣减库存(修改库存表)
- 用户积分变动(更新用户积分)
- 发送消息通知到 Kafka,用于后续物流调度或风控系统消费
这四个操作分别属于不同的服务,并且每个服务都有自己的数据库。那么问题来了——如果其中一个失败了,比如库存扣减失败,那前面的操作是否应该回滚?如果不回滚,会不会导致数据不一致?
于是,我们就不得不面对这个问题:如何保证这些分布在不同服务中的事务操作要么全部成功,要么全部失败?
也就是所谓:“分布式事务”。
问题描述:遇到的挑战和痛点


在实际项目中,一开始我们采用的是本地事务 + 补偿的方式处理这个问题。例如:
- 在下单接口里依次调用:
- 订单创建
- 库存扣减
- 如果某一步出错,在 catch 块里回滚前面的操作,比如手动加库存。
- 用定时任务做数据对账补偿。
听起来挺合理吧?但现实远比想象复杂。
实际遇到的问题包括:
- 幂等性难处理:下游服务重复收到请求怎么办?特别是网络超时重试的情况。
- 状态一致性难以保障:比如库存扣减失败,但订单已生成,如何快速恢复?
- 人工介入频繁:经常需要运营手工修复数据异常。
- 性能瓶颈明显:串行调用效率低,特别是在大促期间 QPS 上不去。
- 缺乏统一的事务管理机制:整个流程松散,维护成本高。
这些痛点让我深刻意识到:这种“土办法”迟早会崩盘。必须引入一套更系统化的分布式事务解决方案。
解决方案:我们尝试了哪些技术?

结合当时的团队规模、项目阶段和运维能力,我们评估了几个主流的分布式事务方案:
| 方案名称 | 类型 | 优点 | 缺点 |
|---|---|---|---|
| 两阶段提交(2PC) | 强一致性 | 数据强一致,适合金融类交易 | 对资源锁定严重,性能差,存在单点故障风险 |
| TCC(Try-Confirm-Cancel) | 柔性事务 | 灵活,可扩展性强 | 开发成本高,需设计正反向操作 |
| SAGA 模式 | 长周期事务 | 实现简单,异步友好 | 易于产生脏数据,补偿逻辑复杂 |
| 最终一致性(如 RocketMQ 半消息) | 最终一致性 | 性能好,适合高并发 | 存在短时间不一致的风险 |
权衡之后,我们选择了 TCC 模式作为主力方案,搭配部分场景使用 SAGA 和 RocketMQ 消息异步解耦。
这里我重点讲讲我们是如何落地 TCC 的。
TCC 模式的实现思路和关键点

TCC 是一种补偿性事务模式,分为三个核心步骤:
- Try 阶段:资源检查与冻结(预占),不会真正执行业务操作;
- Confirm 阶段:确认提交,执行实际业务动作;
- Cancel 阶段:取消/回滚,释放 Try 阶段冻结的资源;
以我们的下订单为例:
Try:
- 冻结库存
- 预留用户积分余额
- 创建未付款订单
Confirm:
- 减库存
- 减积分
- 订单标记为已付款
Cancel:
- 解冻库存
- 取消预留积分
- 订单回退状态
整个流程由一个事务协调器(我们使用 Seata 的 AT 模式做了一些适配)来驱动,记录全局事务 ID 和各分支状态,一旦某个服务抛异常或超时,则自动触发 Cancel 回滚。
实战代码示例
下面是一个简化版的 TCC 接口定义和实现:
// 定义 Try、Confirm、Cancel 方法的接口
public interface InventoryService {
// Try: 冻结库存
boolean prepareInventory(Long productId, int count);
// Confirm: 扣库存
boolean commitInventory(Long productId, int count);
// Cancel: 解冻库存
boolean cancelInventory(Long productId, int count);
}
我们在订单服务中调用:
@Transactional
public void placeOrderWithTcc(Long userId, Long productId, int quantity) {
String xid = UUID.randomUUID().toString();
try {
// 1. Try 阶段:冻结资源
inventoryService.prepareInventory(productId, quantity);
userService.deductPoints(userId, 100); // 同样支持 Try 语义
// 2. Confirm 阶段:确认操作
inventoryService.commitInventory(productId, quantity);
userService.confirmPoints(userId, 100);
// 创建订单
orderService.createOrder(userId, productId, quantity, xid);
} catch (Exception e) {
// 3. Cancel 阶段:执行回滚
inventoryService.cancelInventory(productId, quantity);
userService.rollbackPoints(userId, 100);
throw new OrderPlaceFailedException("下单失败:" + e.getMessage());
}
}
当然,实际生产中远没有这么简单。我们需要考虑:
- 幂等性处理:同一个 XID 多次请求只执行一次
- 日志追踪:方便排查事务失败原因
- 状态持久化:将每一步的状态保存下来以便自动恢复
- 集成限流降级:避免雪崩效应
实战中踩过的那些坑
坑一:Cancel 方法本身也可能会失败
我们在测试环境模拟过这种情况:Commit 成功,但是 Cancel 失败,导致资源一直无法释放。最后的做法是:
- 把 Cancel 操作记录进 DB,并设置一个“回滚失败”的标记;
- 由定时任务异步处理这些失败项,最多尝试三次;
- 超过三次的进入告警队列,人工介入。
坑二:幂等性没做好导致库存被多扣
有一次线上发现库存莫名其妙少了,后来查日志发现是因为:
- 第一次请求 Commit 库存时超时了;
- 客户端重试后再次调用 Commit;
- 而此时之前的 Commit 已经生效了。
解决方法是:每次 Commit / Cancel 操作带上 XID 和唯一标识符,数据库加唯一索引防止重复提交。
坑三:事务超时控制不合理导致雪崩
早期我们在一个事务里做了很多不必要的操作,导致事务链过长,最终触发大量超时,形成连锁反应。
优化手段是:
- 对长事务进行拆分,按优先级划分独立事务组;
- 设置合理的超时阈值(比如主流程不超过 5s);
- 增加熔断机制,失败次数过多时自动暂停部分非核心操作。
效果总结:实施后的收益
引入 TCC 模式后,我们系统的整体稳定性有了显著提升:
- 数据一致性显著提高:相比之前的手动补偿机制,出错率下降了约 85%
- 异常处理自动化程度加强:90%以上的错误可以自动兜底,仅少量需人工干预
- 高峰期表现稳定:双十一期间处理上百万笔订单,零重大事故
虽然 TCC 模式开发工作量较大,但它带来的可靠性是我们不能忽视的。
给新手朋友的经验建议
如果你正在面对分布式事务问题,或者正在准备学习相关知识,我给你几点来自亲身经历的建议:
1. 不要一开始就追求“银弹”
很多人上来就想用 2PC 或者 Seata 全家桶,但其实很多时候根本不需要。
- 单一数据库内的多个操作?用本地事务即可。
- 涉及多个微服务但容忍一定延迟?可以用消息异步补偿。
- 只有强一致性要求高的场景才考虑 TCC、SAGA 这类方案
别让过度设计拖慢你的进度。
2. TCC 设计的核心在于“幂等 + 可逆”
这是我在写完几十个 Try/Confirm/Cancel 方法后总结出来的:
- 任何一步都需要考虑重复执行的影响;
- Cancel 必须能安全回滚 Try 操作;
- 尽量把业务逻辑与事务控制分离。
你可以用 AOP、模板方法等方式封装通用逻辑,减少样板代码。
3. 做好监控和日志埋点
分布式事务的调试难度远高于本地事务。我们曾因为少了一条 XID 的日志追踪,花了一整天定位问题。所以一定要做到:
- 每个事务环节都记录上下文日志(XID、服务名、耗时、参数)
- 事务状态变化实时写入数据库或日志系统
- 结合 Grafana + Prometheus 展示事务成功率、回滚率等指标
4. 别忽视“降级”设计
哪怕是最健壮的事务流程,也要考虑极端情况下的“优雅失败”。比如:
- 当库存中心不可用时,是否允许继续下单?还是直接返回错误?
- 是否可以通过缓存库存临时支撑?
- 降级后是否支持异步补偿?
这些都需要结合业务目标一起评估。
5. 学会组合使用多种事务模式
并不是所有场景都适用 TCC,有些适合用 RocketMQ 的半消息机制,比如:
- 下单完成后发送通知给物流;
- 支付回调后触发发放优惠券;
而有些又适合用 SAGA 模式,比如:
- 涉及多个外部系统的审批流;
- 异步操作较多的场景;
学会根据场景选择合适的组合方案,才是工程之道。
写在最后:关于未来趋势的一些思考
现在,随着云原生和 Serverless 的发展,像 AWS Step Functions、阿里云 Saga Flow 这样的状态机编排工具越来越成熟,为我们提供了新的可能。
不过,无论技术怎么演进,本质还是围绕:状态管理、补偿机制、可观测性。
我觉得未来的方向可能是:
- 更智能的状态流转引擎;
- 更完善的内置补偿模型;
- 更轻量的事务框架集成;
- 更多基于 AI 的自动兜底策略;
但在现阶段,我们依然需要脚踏实地,从每一个 Try/Cancel 开始,构建可靠的服务边界和清晰的事务边界。
希望这篇文章能让你对分布式事务的理解不再停留在“听说过”,而是能真正在实践中用得起来。如果你还有其他问题,欢迎留言交流。毕竟这条路,我们都走过,都踩过坑,也都一步步走出来了。
共勉!

评论 0