分布式事务解决方案:最佳实践
分布式事务实战:从踩坑到落地的经验分享
说到分布式事务,我想每个经历过微服务架构的开发者都会有那么一段“痛苦”的回忆吧。我也不例外,在我们团队的一次关键项目重构中,这个问题直接让我们在上线前卡了整整一周的时间。当时那种焦虑、无奈和后来解决后的如释重负,至今记忆犹新。
今天我就想用第一人称的方式,结合那次真实项目经历,跟你聊聊我在处理分布式事务方面的一些经验、踩过的坑,以及最终落地的解决方案。不是理论堆砌,而是真·实战经验分享,希望能对正在或即将遇到类似问题的你有所帮助。
背景介绍:为什么我们需要面对分布式事务?

事情要从我们公司内部的一个订单系统重构说起。
原来的系统是一个单体应用,运行在一个数据库上,所有操作都在一个事务里完成,简单粗暴但稳定。随着业务发展,我们开始拆分模块,逐步往微服务方向演进。订单中心独立出来了,库存中心、支付中心也陆续变成了独立服务,并各自拥有自己的数据库。
这时候,一个新的订单创建流程就涉及三个核心服务:
- 订单服务:创建订单
- 库存服务:减库存
- 支付服务:处理支付动作
这三步操作必须满足“要么都成功,要么都失败”,否则就会出现数据不一致的问题。比如订单创建了,库存也扣了,但支付出错导致整个交易失败,这就可能造成用户账户被划款而商品没发货,进而引发投诉和纠纷。
于是——典型的分布式事务场景来了。
初期方案与挑战:本地事务 + 消息补偿

我们最初的方案是使用“本地事务+消息队列”做异步补偿机制:
- 订单服务插入订单记录(本地事务)
- 向MQ发送一条库存扣除的消息
- 库存服务监听消息,执行扣减库存
- 后续支付服务由另一个消息触发
这样做的好处是系统解耦,性能也能撑得住大并发。但问题也很快暴露出来:
- MQ丢了消息怎么办?
- 如果消息重复消费了,会不会多扣库存?
- 万一订单服务崩溃了还没发消息出去呢?
我们尝试通过幂等处理+本地事务表来兜底,但在实际压测和测试环境下,依然出现了大量的状态不一致情况。比如库存已经扣完,但订单没创建成功;或者订单创建成功,库存没有及时扣掉。
这种情况下,系统需要人工介入修复数据,显然是不可接受的。
最终解决方案:引入TCC型分布式事务框架

经过多方调研和技术选型,我们最终决定采用一种基于TCC(Try - Confirm - Cancel)模式的分布式事务框架来解决这个问题。
我们选择的是开源的 Seata 框架,它支持AT、TCC、SAGA等多种事务模式。考虑到我们已经有多个独立服务,并且数据库类型多样(MySQL、PostgreSQL都有),TCC模式更适合我们当前的情况。
TCC模式的核心思想是:
- Try 阶段:资源预留(冻结库存、检查余额等)
- Confirm 阶段:真正执行业务逻辑(扣库存、扣余额)
- Cancel 阶段:回滚操作(解冻库存、恢复余额)
这种方式虽然比本地事务复杂一些,但也更灵活,尤其是在服务隔离度较高、跨系统的场景下。
实施过程:一步步搭建TCC事务链路

接下来我会结合代码示例来说明我们的实现思路。注意,这部分是我根据当时的项目改写出来的简化版本,供参考。
一、定义TCC接口契约
首先我们要为各个服务定义好TCC的业务接口。以库存服务为例:
public interface InventoryService {
// Try阶段:冻结库存
boolean deductInventory(String productId, int quantity);
// Confirm阶段:正式扣库存
boolean confirmDeduct(String txId, String productId, int quantity);
// Cancel阶段:回滚
boolean cancelDeduct(String txId, String productId, int quantity);
}
这里的txId是全局事务ID,由Seata服务端统一分配。
二、集成Seata客户端配置
Spring Boot项目的application.yml配置如下:
seata:
enabled: true
application-id: order-service
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP

三、业务方法添加全局事务注解
在订单创建入口处添加全局事务控制:
@GlobalTransactional
public Order createOrder(String userId, String productId, int quantity) {
// Step 1: 创建订单(本地事务)
Order order = orderDao.create(userId, productId, quantity);
try {
// Step 2: 扣库存(TCC方式)
inventoryService.deductInventory(productId, quantity);
// Step 3: 扣支付(TCC方式)
paymentService.charge(userId, productId, quantity);
return order;
} catch (Exception e) {
log.error("下单失败", e);
throw new RuntimeException("下单失败");
}
}
这段代码看起来很简单,但实际上背后Seata会自动帮你管理事务状态,并在失败时调用Cancel方法进行回滚。
四、服务间调用加全局上下文传播
我们在Feign调用中,加入了Seata的上下文拦截器,确保每次远程调用都带上txId等事务信息:
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor seataRequestInterceptor() {
return requestTemplate -> {
String xid = RootContext.getXID();
if (xid != null) {
requestTemplate.header("XID", xid);
}
};
}
}

接收方也需要有对应的拦截器解析这个Header并设置上下文,这样Seata才能串联起整个事务链。
踩坑经验:那些让我头大的事
再牛的框架,用不好照样翻车。我们在实践中踩了不少坑,下面挑几个比较重要的说说。
坑点1:Cancel方法幂等问题没处理好
刚开始我们写的Cancel方法没考虑幂等性,结果出现过多次Cancel被重复调用,导致库存变成负数。后来我们加上了一个事务ID + 状态标志位的方式:
@Override
public boolean cancelDeduct(String txId, String productId, int quantity) {
if (txLogService.isTxProcessed(txId)) {
return true; // 已处理过,避免重复操作
}
// 实际回滚逻辑
inventoryDao.increase(productId, quantity);
txLogService.markAsProcessed(txId);
return true;
}
坑点2:事务超时时间设置不当
Seata默认的事务超时时间是60秒,但我们有个第三方支付接口响应非常慢,经常超过这个时间。后果就是Cancel被调用,钱又退回来了,但支付却最终成功了。
解决办法有两个:
- 设置长一点的超时时间:
@GlobalTransactional(timeoutMills = 300000) - 改成异步通知机制,避免同步阻塞等待支付结果
坑点3:生产环境Seata Server故障导致事务阻塞
我们线上部署时曾遇到一次Seata Server异常重启的情况。此时大量事务处于“未知”状态,导致部分订单无法继续执行。
我们后来做了一套定时巡检事务日志的机制,定期拉取未完成事务的状态,并主动发起Cancel或Confirm,缓解了这个问题。
实施效果与收益:不仅仅是技术提升
这套TCC事务方案上线后,我们的系统表现明显稳定了很多:
- 数据一致性得到了保障,基本不再有人工介入修复
- 事务追踪能力提升,出了问题能快速定位到哪一步出了异常
- 在高并发下表现良好,QPS达到了预期目标
更重要的是,整个团队对分布式系统的设计有了更深入的理解,特别是在:
- 服务边界设计
- 事务划分粒度
- 幂等性与并发控制等细节上的把控
经验总结:给开发者的几点建议
如果你也在面临分布式事务的问题,以下几点是我的真诚建议:
✅ 优先保证业务可补偿性
不是所有业务都能用强一致性保证,有些场景适合弱一致性配合异步补偿。比如统计类、非关键路径的操作,可以牺牲一点一致性,换性能和可用性。
✅ 事务粒度不宜过大
尽量把事务切小,减少锁竞争。不要把整个下单流程放到一个事务里,能拆的就拆开。比如用户身份验证可以放外面,只有核心操作走事务。
✅ 异常监控要提前做好
分布式事务一旦出问题,排查起来代价很高。务必要有一个完善的监控体系,包括但不限于:
- 全局事务日志跟踪
- 服务调用链分析
- Cancel/Confirm调用次数报警
✅ 不要迷信某个框架,适合自己最重要
Seata是个不错的工具,但它不一定适合你。比如有的团队用了阿里云的GTS,也有人自研轻量级事务机制。关键是结合自己业务特点和架构水平做出选择。
结语:分布式事务没有银弹,只有权衡
写到这里,我想说的是:分布式事务从来不是一个轻松的技术话题。它的本质是在分布式系统中找到一个合理的平衡点:一致性 vs 可用性、复杂度 vs 稳定性、成本 vs 安全。
在我们这次项目落地过程中,我也深刻体会到:技术再厉害,也需要工程化思维去支撑。每一次“踩坑”,其实都是对系统设计的又一次反思和优化。
希望这篇文章能给你带来一些启发。如果你也在分布式事务这条路上挣扎过、困惑过,欢迎留言交流,我们一起成长!
📌 附录:推荐阅读资料
- Seata官方文档:https://seata.io/zh-cn/docs/overview/what-is-seata/
- 《从零开始学架构》- 李运华 著
- 《企业IT架构转型之道:阿里巴巴中台战略及其架构实践》- 钟华 著

评论 0