分布式事务解决方案:那些踩过的坑与积累的经验
引子:一个看似简单的需求

去年年底,我所在的团队接到了一个新需求:上线“秒杀+退款”功能。听起来很常见吧?但问题在于,这个系统并不是简单的单体应用,而是基于微服务架构构建的一套电商交易系统。订单、库存、支付、会员积分等模块分别部署在不同的服务中,各自拥有独立的数据库。
当时我的第一反应是:“这不就是个分布式事务的问题嘛。”可真做起来才发现,理论和实践之间,隔着不止一条河。
这篇文章我想以自己参与的一个真实项目为例,从实际出发聊聊我们在处理分布式事务时遇到的一些挑战、采用的方案以及从中总结的经验。希望对正在或即将面对类似问题的同学有所帮助。
项目背景介绍:从“单体”到“分布式”的转型

我们是一家垂直电商平台,早期业务相对单一,订单、商品、库存都在一个库中,整个交易流程都是本地事务保证一致性,基本没有并发问题。
后来为了支撑更高并发、更快迭代速度,我们逐步拆分出多个服务:
- 订单服务(Order)
- 支付服务(Payment)
- 库存服务(Inventory)
- 会员积分服务(Points)
每个服务都有自己的数据库,而且为了扩展性和性能,还采用了读写分离和部分数据缓存策略。
在这个背景下,“用户下单并完成支付”的流程就涉及到跨服务的协调操作:
- 扣减库存(Inventory Service)
- 创建订单(Order Service)
- 发起支付(Payment Service)
- 积分增加(Points Service)
这些操作必须全部成功或者全部失败,否则就会出现“钱收了但是库存没扣”、“积分加了但订单取消了”等严重问题。
这时候我们就不得不认真对待分布式事务的问题了。
面临的挑战:不是每个场景都适合两阶段提交

挑战一:传统XA协议太重
一开始我们尝试用XA协议来统一管理所有数据库资源,也就是典型的两阶段提交(2PC)。但我们很快就发现这套机制在生产环境几乎不可行:
- 网络抖动导致协调器挂掉后恢复困难
- 锁资源时间长,影响性能
- 很多数据库或中间件压根就不支持XA
特别是我们的支付服务使用的是第三方支付网关,无法参与到XA事务中,这就意味着这条链路根本没法走完整个ACID事务流程。
挑战二:TCC模式开发成本高,逻辑复杂易出错
既然XA不行,那我们考虑使用TCC模式(Try-Confirm-Cancel),这是目前比较主流的分布式事务解决方案之一。
但在实际开发过程中,我们遇到了几个问题:
- Try操作需要预留资源,比如先冻结库存而不是直接扣除,但这会引入超时自动释放、幂等性等额外逻辑。
- Cancel补偿机制要可靠且可重试,一旦Cancel失败就需要不断重试,否则可能导致状态不一致。
- 代码层面实现复杂,我们需要为每一个操作编写 Try + Confirm + Cancel 的三组函数,并且还要处理各种异常情况。
更糟的是,在实际测试中,我们发现如果某个服务宕机较久,会导致Cancel堆积,最终系统压力剧增甚至崩溃。
挑战三:事件驱动下的一致性保障难以闭环
后来我们尝试引入事件总线(Event Bus)来进行异步解耦,比如“订单创建完成后发送消息通知库存扣减”。但这种方式本质上是最终一致性,无法满足强一致性的退款流程。
尤其是在退款这种需要同时修改订单状态、返还库存、退回积分、撤销优惠券的场景下,这种设计风险很高,稍有不慎就会遗漏某些补偿步骤。
我们的解决方案:混合使用 Saga 模式 + 消息队列 + 状态机驱动 + 人工兜底
经过多轮讨论和技术验证,我们最终采取了一种折中的方式,结合多种技术手段来实现最终一致性与容错能力:
方案一:Saga 模式作为主流程控制
我们选择了Saga Pattern,它非常适合于长周期、多服务交互的场景。
举个例子,在支付流程中,各环节执行顺序如下:
[发起支付] → [冻结库存] → [生成订单] → [调用支付网关] → [增加会员积分]
如果任意一步出错,就按反向顺序进行补偿:
[取消积分] → [作废订单] → [解冻库存] → [撤销支付记录]
这种方式的好处是:
- 不会持有全局锁
- 各服务之间完全解耦
- 可以通过异步消息进行驱动
方案二:状态机驱动业务流程
为了更好地管理状态流转和错误回滚,我们引入了一个状态机引擎。核心思想是:把整个业务流程抽象成一系列状态节点,每一步的操作由状态触发,每一步的成功与否决定是否进入下一步。
例如,订单的状态从“待付款” → “已付款” → “已发货” → “已完成”,而每个状态转移都对应一个服务调用或本地事务操作。
这样做的好处是:
- 明确每一步的执行顺序和依赖关系
- 状态持久化之后便于查询和故障排查
- 出现异常时可以重新加载当前状态继续处理
我们选用的是轻量级状态机框架(如Spring StateMachine),也有人选择将状态机交给单独的服务来管理,视具体情况而定。
方案三:消息队列解耦 + 死信队列监控
为了提升系统的响应速度和可靠性,我们将大部分非实时操作通过消息队列异步化:
- 库存更新、积分变动、优惠券发放等都通过 Kafka 发送异步消息
- 每个消费者监听各自的Topic,按需处理业务逻辑
- 消费失败的消息进入死信队列,并通过后台报警通知处理人员
这样一方面提高了整体吞吐量,另一方面也减少了各服务之间的直接依赖。
但需要注意的是:
- 消息消费必须幂等
- 要设置合理的重试机制(比如指数退避)
- 死信队列要定期清理或人工介入
方案四:定时任务核对 + 数据修复平台
由于 Saga 模式只是“尽力而为”的最终一致性,所以我们还建立了一套定时任务来做数据核对和兜底。
每天凌晨,我们会运行一次全量数据核对脚本,检查关键业务字段是否一致,例如:
- 库存数量是否与订单明细匹配?
- 支付金额是否与积分兑换记录相符?
- 订单状态与支付状态是否同步?
一旦发现问题,就触发告警并通过内部的数据修复平台进行手工干预。
实施后的效果:稳定、灵活、可控
经过几个月的改造和灰度上线,我们终于实现了预期的效果:
- 系统可用性显著提升:即使某个服务暂时不可用,也不会影响其他服务的正常工作
- 错误处理更加清晰可控:通过状态机和日志记录,我们能快速定位问题点并进行修复
- 运维成本降低:数据核对自动化,人工兜底频率大大减少
- 开发效率提高:虽然初期投入大,但后续新加流程只需定义状态和动作,接入即可
值得一提的是,这套机制还为我们后续的“退货退款”、“售后补偿”等功能打下了良好的基础。我们可以复用已有的状态模型和事务处理逻辑,大幅缩短开发周期。
给你的建议:别试图找“银弹”
在整个项目推进过程中,我也有一些心得体会想分享给大家:
✅ 1. 没有一种方案能解决所有问题
无论是TCC、SAGA、还是基于消息的最终一致性,它们都有适用的场景。你得根据业务复杂度、对一致性的要求、性能压力等因素综合判断。
例如:
- 对一致性要求极高的金融场景(如转账),可能更适合使用 TCC;
- 对响应速度要求高但容忍短时间不一致的电商系统,Saga 或 Event Sourcing 更合适;
- 如果你追求极致的性能和低延迟,只能接受最终一致,那么纯事件驱动+定时补偿可能是首选。
✅ 2. 不要低估开发和维护成本
实现分布式事务并不是引入一个框架或组件那么简单,它会深刻影响整个系统的架构设计、接口定义、日志体系和异常处理机制。
你需要:
- 定义清楚每一个服务的职责边界
- 设计好幂等、重试、补偿机制
- 做好数据可观测性(日志、指标、链路追踪)
- 建立完善的监控报警和运维工具
否则,很容易陷入“救火”状态。
✅ 3. 最终一致性 ≠ 放弃一致性
有些人认为用了消息队列就只需要最终一致性了,其实不然。我们要在“一致性”和“可用性”之间找到平衡,不能一味追求高性能而忽视业务正确性。
如果你的系统对某类操作有一致性硬要求,就必须引入补偿机制或同步校验机制,哪怕代价是牺牲一些性能。
✅ 4. 日志和监控是你的“救命稻草”
在我经历的项目里,日志系统的重要性怎么强调都不为过。我们专门设计了“事务ID”贯穿整个流程,方便后期跟踪;也建立了完整的埋点和报表体系,帮助我们分析系统健康状况。
所以建议你在开发的时候就考虑:
- 是否可以在整个链路中传递上下文ID
- 是否有明确的日志记录规范
- 是否接入APM工具(如SkyWalking、Pinpoint、Zipkin等)
✅ 5. 提前准备好数据修复工具和应急预案
即便做了再多的设计,系统也不可能做到绝对无bug。我们曾因为一次版本升级导致状态机跳转错误,造成一批订单状态不一致。幸好我们有定时核对机制,及时发现了问题,并通过内部的“数据修复平台”快速完成纠正。
所以一定要有一个通用的数据比对脚本库、修复工具集,甚至是可视化界面,方便运维同事快速介入处理。
小插曲:一次诡异的幂等性失效
讲个小插曲,希望能引起大家共鸣。
有一次上线后,我们收到了大量“重复扣减库存”的报警。经过排查发现,某个服务在接收到Kafka消息后进行了重试,但由于幂等key设置不准确,导致同一笔订单的两条消息被当成了两个请求处理。
这个问题提醒我们:幂等性不是一句口号,是要靠严谨设计去实现的。
我们在那次事故后制定了几条规则:
- 每次外部请求都要带上唯一标识(requestId)
- 所有幂等操作必须记录到DB或Redis中
- 清晰区分“业务幂等”和“网络重试”
- 所有补偿操作也必须具备幂等能力
结语:保持敬畏心,持续优化架构
最后我想说,分布式事务从来都不是技术上的“小case”,它是我们架构师必须认真面对的一项核心挑战。每一个决策的背后,都是对系统理解、团队协作、业务优先级的综合权衡。
我也不奢望在这篇文章里就能给出一个放之四海而皆准的完美方案,但我希望通过自己亲身经历的案例,分享一套可行的思路和经验。
希望你能从中学到一些实用的东西,少走些弯路。
如果你也在实践中遇到类似的难题,欢迎留言交流,我们一起探讨更好的解决方案。

评论 0