分布式事务解决方案:我的五年实战心得
开篇:分布式系统带来的“幸福”烦恼

在做后端开发这五年里,我经历了从单体应用到微服务架构的完整演进。如果说微服务带来了架构灵活性和可扩展性,那它也带来了一个绕不开的问题:分布式事务怎么处理?
这个问题,在我参与公司核心订单系统的重构时被推到了台前。原本跑得挺好的单体系统,随着业务量的增长逐渐显现出瓶颈,我们决定将库存、支付、订单三个模块拆成独立服务。结果第一周就踩到了坑——用户下单扣了钱,但库存没扣住,最后只能手动回滚数据。
这篇文章就来说说我在实际工作中对分布式事务的一些思考与实践,希望能帮大家少走弯路。
问题描述:一次生产事故引发的血案

事情是这样的:
我们上线了新的微服务架构,新功能支持优惠券叠加使用、分段支付等功能。但在某个大促活动上线不久后,客户投诉自己钱包余额减少了,订单状态却一直是“待支付”。更糟的是,后台日志显示支付服务调用了,但库存没扣减成功。
当时我们还没引入任何专门的分布式事务框架,仅靠本地事务 + 接口回调的方式来处理一致性问题。这种方案在正常流程下还能应付,一旦遇到异常情况(如网络超时、接口失败、重试冲突等),数据就很容易处于中间状态。
这其实就是典型的分布式事务问题:多个服务之间需要保证ACID特性,但由于没有统一的事务协调者,最终导致数据不一致。
解决方案:不是只有TCC!
说到分布式事务,很多人的第一反应就是:TCC!Seata!Saga模式!没错,这些确实是主流方案,但从我五年的经验来看,并不是所有的场景都适合用这些重型武器。
我总结出了几种常用的分布式事务处理方式,并结合不同项目背景选择合适的方案:
1. 最简单的办法——最终一致性(Eventual Consistency)
适用于高并发读写多、但对强一致性要求不高的场景。比如商品浏览记录、推荐行为统计等。
做法就是在发生变更后通过MQ异步通知其他服务完成更新。虽然有可能短暂不一致,但大多数情况下可以接受。
// 发送消息示例
rocketMQTemplate.convertAndSend("ORDER_PAY_SUCCESS_TOPIC", orderDTO);
监听方消费消息后,更新自己的状态即可。
这种方式简单粗暴、性能好,缺点是没有实时一致性保障,必须做好消费重试机制和幂等校验。
2. 基于 TCC 的补偿型事务
这是我们后来为订单系统做的正式方案,因为涉及到资金操作,必须确保数据强一致。
我们采用了阿里巴巴的开源分布式事务框架 Seata,并通过 TCC 模式来实现跨服务的一致性控制。
具体思路如下:
- 用户下单 → 订单服务创建预订单
- 调用库存服务进行冻结库存(Try阶段)
- 调用支付服务尝试锁定账户余额(Try阶段)
- 所有 Try 成功 → 提交订单并进入 Confirm 阶段,释放资源
- 若任一 Try 失败,则执行 Cancel 操作回滚
伪代码逻辑示意:
try {
inventoryService.freezeStock(orderDTO.getProductId(), orderDTO.getAmount());
paymentService.lockBalance(userId, orderDTO.getTotalPrice());
// 写入订单表,status=pending
orderRepository.save(pendingOrder);
} catch (Exception ex) {
inventoryService.cancelFreezeStock(...);
paymentService.rollbackLockedBalance(...);
}
当然,这只是个简化版本,真正落地要考虑幂等、事务日志、重试机制等多个方面。
3. Saga 模式:长流程中的事务编排
我们在做供应链管理系统时遇到了一个特殊场景:一笔采购订单要依次经过供应商确认、质检、入库等环节,每个步骤都可能失败,需要灵活回退。
这种时候我们选择了 Saga 模式,即将整个流程拆分为一系列本地事务,每一步都提供正向操作及补偿动作。
例如:
- 创建订单 → 补偿动作:删除订单
- 供应商确认 → 补偿:撤销确认
- 入库记录 → 补偿:撤销入库
我们使用 Camunda 作为工作流引擎来驱动整个流程,不仅解决了事务一致性问题,还实现了流程可视化管理和人工审批介入。
代码实践:基于 Seata 的 TCC 示例
以下是一些关键代码片段(Spring Boot + MyBatis + Seata):
添加依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
注解开启全局事务
@GlobalTransactional(name = "create-order-tx")
public void createOrder(OrderRequest request) {
inventoryService.deduct(request.getProductId(), request.getCount());
paymentService.charge(request.getUserId(), request.getAmount());
orderRepo.create(new Order(...));
}
实现 TCC 接口(以库存为例)

@Component
public class InventoryTccAction {
@Autowired
private InventoryMapper inventoryMapper;
@TwoPhaseBusinessAction(name = "deductInventory")
public boolean deduct(InventoryDTO dto) {
Integer count = inventoryMapper.selectCountById(dto.getProductId());
if(count < dto.getCount()) throw new RuntimeException("库存不足");
return inventoryMapper.updateAmount(dto.getProductId(), -dto.getCount()) > 0;
}
@Commit
public boolean commit() {
// 真实业务中此处会做一些清理操作,比如写日志
return true;
}
@Rollback
public boolean rollback() {
// 这里执行补偿逻辑
return true;
}
}
注意点:
- Try 阶段一定要是幂等的
- Confirm/Cancel 阶段也需要是幂等的,否则重试可能导致重复操作
- 所有远程调用都需要捕获异常并主动回滚
踩坑经验:那些年我掉过的坑

❌ 1. 忽略幂等设计,导致重复扣款
有一次线上环境出现消息重复消费的情况,导致同一个订单被执行了两次支付操作。后来我们才意识到所有事务操作都必须加上幂等判断。
我们后来的做法是在数据库增加一个业务唯一键字段(如business_id),并在每次操作前先查询是否存在该记录。
INSERT INTO payments (order_id, amount) VALUES (?, ?) ON DUPLICATE KEY UPDATE status='DUPLICATE';
❌ 2. 事务提交顺序不当,导致死锁
刚开始用 Seata 的时候,我们同时调用了支付服务和积分服务,两个服务都有各自的数据修改操作,且互相等待对方释放资源,导致死锁。
解决办法:
- 明确事务资源的操作顺序,避免交叉加锁
- 引入 Redis 缓存或队列,降低直接耦合度
❌ 3. 未合理配置重试次数,压垮下游服务
有一阵子我们开启了无限重试+大量并发请求,导致下游库存服务 CPU 直接飙满,服务不可用。
调整后策略:
- 重试次数限制(最大 3 次)
- 使用指数退避算法控制重试间隔时间
- 加入熔断机制(Hystrix 或 Sentinel)
效果总结:稳定性显著提升
这套方案实施后,我们的系统稳定性有了明显改善:
- 支付 + 库存 + 订单三者之间的数据一致性得到了保障
- TCC 模式的引入使关键路径操作具备回滚能力
- 生产环境运行半年以上,没有因事务问题导致的用户投诉
- 在促销期间 QPS 上升 40%,系统仍然保持稳定
另外,配合运维的同学搭建了事务监控平台,可以实时查看每笔事务的状态流转,提升了排查效率。
经验分享:别只盯着技术,更要关注业务
如果你问我这几年最大的感悟是什么,我会说:
“最好的事务设计方案,从来不是某一个流行框架,而是你对业务的理解。”
举几个我踩过的例子:
- 当初为了追求一致性,强行用 TCC 来管理商品评论的同步,结果发现根本不需要。
- 有些流程本身就不应该在一个事务里完成,强行绑定只会让系统变得更脆弱。
所以我建议:
- 先从业务角度出发,评估是否需要强一致性
- 优先选用轻量级方案,能不用TCC就不上
- 无论哪种方案,都必须考虑幂等、重试、熔断
- 设计事务边界时要考虑未来扩展性和运维成本
顺便提一句,现在越来越多团队开始采用事件溯源 + CQRS 的方式来处理复杂的事务场景,我个人也在关注这类架构,也许下个项目会尝试一下。
结语:技术没有银弹,合适才是关键
写完这篇,我自己也回顾了一下这些年在分布式事务上的探索之路。有时候我们会陷入一个误区,觉得只要上了 Seata 或别的框架就可以一劳永逸地解决问题,其实不然。
真正难的不是技术本身,而是如何根据实际情况做出取舍。希望这篇文章能给你一些启发,少走一点弯路,少改几次需求。
如果你也有类似的实践经验,欢迎留言交流,我们一起探讨分布式事务的最佳落地之道。
本文基于我在某电商公司的实际项目经历撰写,如有雷同,纯属巧合 🐱

评论 0