从踩坑到填坑:我在分布式事务上的实战经验分享
引言:为什么分布式事务这么重要?

刚进公司那会儿,我接手了一个电商系统的订单模块。当时系统已经跑了一阵子,用户量也不算小。最开始的架构比较简单,订单服务和库存服务各自独立部署,通过RPC调用进行通信。比如下单操作需要先扣减库存,再创建订单记录。
听起来挺合理的对吧?但问题来了:当扣减库存成功后,如果创建订单失败了怎么办?
这可就悲剧了,库存被扣了,订单却没有生成,用户肯定会投诉——而且数据还不一致。更麻烦的是,这种场景在高并发下特别容易出现,尤其是在网络不稳定、服务宕机等情况下,问题会被放大。
于是,我们团队不得不面对一个老大难的问题:如何在微服务架构下保证多个服务之间的数据一致性?
这就是今天要聊的话题:分布式事务。
问题描述:我们的“经典”故障现场

说个真实的例子。去年双11前压测期间,我们在模拟秒杀场景时发现一个严重问题:
用户A参与抢购活动,商品价格是50元。
- 库存服务检查有货,执行减库存(原子操作);
- 订单服务插入订单记录;
- 支付服务发起支付请求;
但在步骤2插入订单时超时了,虽然后续重试成功,但因为没有幂等性设计,导致用户实际被扣了两次钱!
这不是简单的业务错误,而是典型的分布式系统下的一致性问题。
主要挑战包括:
- 多个服务间的数据操作必须要么全部成功,要么全部回滚;
- 系统需要容忍一定的延迟和部分失败;
- 不同数据库无法直接使用本地事务;
- 需要考虑性能开销,尤其是高并发场景。
我们不能接受这类数据不一致带来的损失,必须找出合适的解决方案。
解决方案:选择合适的技术方案

我们团队最初尝试了几种方式:
1. 最终一致性(Eventual Consistency)
我们尝试通过异步消息队列来解耦服务,比如用 RocketMQ 发布事件,下游服务监听并处理自己的业务逻辑。
但问题是:如果中间任何一个环节失败了,如何回滚其他已发生的变更?
例如:库存已经被扣减了,但如果发完消息后应用宕机,其他服务根本不知道发生了什么,也无法主动回滚。
最终结论:适用于容忍一定不一致性的场景,但不能解决强一致性需求。
2. 使用 TCC(Try - Confirm - Cancel)
TCC 是一种补偿型事务机制,核心思想是:
- Try:资源预留阶段(冻结库存);
- Confirm:业务执行成功,确认资源占用;
- Cancel:执行失败,释放资源;
这个方案看起来很理想,但我们实际开发过程中遇到了一些问题:
案例分享:
我们给库存服务加了个冻结字段,每次下单先做 Try(冻结库存),然后执行订单创建,最后 Confirm。
但随着业务复杂度增加,各种异常分支变多了,代码变得非常臃肿。Cancel 的实现也容易出错,特别是涉及多个服务的情况下,一旦 Cancel 失败还需要不断重试甚至人工介入。
最终结论:适合业务流程清晰且可以预估回退动作的场景,但复杂度较高,维护成本大。
3. 使用 Seata 进行全局事务控制
Seata 是阿里开源的一个分布式事务框架,支持 AT、TCC、Saga 和 XA 模式。
我们最终选择了它的 AT模式,因为它对业务代码入侵最小,几乎不需要修改现有逻辑,只需要加上注解即可开启分布式事务。
实现思路:
- 在订单服务入口方法上添加
@GlobalTransactional注解; - 各服务连接各自的数据库;
- Seata 会在事务提交或回滚时自动协调所有参与方;
- 底层基于 undo_log 表进行回滚操作,对 MySQL 的兼容性非常好。
优点很明显:
- 开发效率高;
- 基本无侵入性;
- 对 DB 友好;
- 社区活跃,文档齐全。
代码实践:简单看一个示例
为了给大家一个直观的感受,这里贴一个简化版的核心代码逻辑:
// 订单服务主入口
public class OrderService {
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private PaymentFeignClient paymentFeignClient;
@GlobalTransactional(timeoutMills = 3000)
public void placeOrder(OrderDTO orderDTO) {
// 调用库存服务(Try阶段)
inventoryFeignClient.deductInventory(orderDTO.getProductId(), orderDTO.getCount());
// 插入订单记录
orderRepository.save(orderDTO.toEntity());
// 调用支付服务(Confirm/Canel由Seata自动处理)
paymentFeignClient.charge(orderDTO.getUserId(), orderDTO.getTotalPrice());
}
}
关键点说明:
@GlobalTransactional注解开启了一个全局事务,Seata 会拦截这些调用,并记录前后镜像用于回滚;- 每个服务都配置了 Seata 的 client,指向 TC(Transaction Coordinator);
- 所有 DB 操作都会自动注册为 RM(Resource Manager);
- 如果任何一步抛出了异常,整个事务都会回滚。
踩坑经验:开发过程中遇到的真实问题
下面是我个人亲身经历的一些“血泪教训”,希望能帮大家少走弯路。
问题一:RM未正确注册导致事务失效
有一次上线新服务后,发现分布式事务没生效,查询 Seata 控制台才发现该服务的 RM 并没有注册上来。
原因排查: 原来该服务在初始化的时候启动太快,在 Seata 初始化完成之前就开始执行 DB 操作,导致事务上下文丢失。
解决办法:
- 加一个初始化标志位,等待 Seata 初始化完成后才开放 DB 请求;
- 或者在 Spring Boot 中将 Seata Starter 设置为启动项优先加载。
问题二:undo_log 数据过大影响性能
某天凌晨报警:某个数据库的 undo_log 表占用空间暴涨,导致主从延迟严重。
根本原因: 长时间运行的长事务较多,或者某些事务频繁提交失败导致多次重试,undo_log 积压过多。
解决方案:
- 增加定时任务定期清理过期的 undo_log;
- 优化事务提交失败后的重试策略,避免无限重试;
- 升级 Seata 到 1.6+,自带清理功能(GC Worker);
- 避免把耗时较长的操作放在全局事务中。
问题三:事务传播失败导致数据不一致
有一次我们发版后出现了数据不一致的情况。排查发现是 Feign 调用链中断,全局事务上下文没有正确传递。
根本原因:
- 使用了 OpenFeign,默认不会透传 Seata 的 XID;
- 若中间夹杂 HTTP 请求,而 Header 没有带上 XID,会导致事务断链;
- 需要在 Feign Client 添加自定义拦截器,手动将 XID 透传下去。
解决方案: 写了一个 Feign Interceptor 自动注入 Seata 的 XID,确保事务传播链完整。
效果总结:上线后的收益与效果
自从引入 Seata 之后,我们系统的几个核心指标有了明显改善:
| 指标 | 上线前 | 上线后 |
|---|---|---|
| 分布式事务失败率 | 1.2% | <0.05% |
| 数据不一致发生次数 | 每周平均 3次 | 几乎无 |
| 事务平均响应时间 | ~80ms | ~120ms(增加了协调开销) |
| 开发迭代速度 | 慢(需处理补偿逻辑) | 快 |
虽然性能略微下降了一些,但从整体稳定性和维护成本来看,收益远大于成本。
经验分享:给开发者的几点建议
结合我的经验和踩过的坑,给正在考虑引入分布式事务的你几条建议:
✅ 选型要匹配业务场景
不同场景适合不同的方案:
- 最终一致性 → 异步 + 消息队列;
- 强一致性 + 结构清晰 → TCC;
- 快速接入、减少改动 → Seata(AT模式为主);
- 长周期任务、状态流转多 → Saga 模式。
别一味追求技术潮流,适合自己才是最好的。
✅ 性能永远是个重点
引入分布式事务必然会带来额外开销,尤其要注意以下几点:
- 事务边界尽可能小,不要包裹无关代码;
- 尽量不在事务内做 RPC/HTTP 请求,尤其是跨地域;
- 监控事务响应时间和失败率,设置合理的熔断降级机制;
- 提供快速回滚工具,防止问题扩散。
✅ 数据库设计也很关键
- 表结构要有版本号/更新时间戳,用于幂等判断;
- 重要操作建议记录日志、流水号,便于追踪;
- undo_log 表单独建索引,加快清理速度;
- 定期备份 undo_log 表内容以防万一。
✅ 接口设计要有兜底能力
- 所有对外接口提供幂等性支持(如带 orderId 去重);
- 每个服务暴露健康检查接口,便于 Seata 查询状态;
- 要支持手动触发回滚或补单的功能;
- 提供可视化工具或命令行工具,辅助运维人员处理异常事务。
✅ 生产环境运维注意事项
- 部署 Seata Server 前务必要做容量评估;
- 建议将 TC(事务协调器)部署成集群(至少3节点);
- undo_log 表要做分区 + 定期清理;
- 监控 TC、RM 的心跳健康状态;
- 使用 Sentinel 控制 Seata 的 QPS,防止雪崩;
- 要设置自动熔断策略,避免大面积雪崩。
写在最后:分布式事务不是万能药
其实这篇文章写了这么久,我也想说一句心里话:
分布式事务不是银弹。它只是帮你掩盖系统复杂性的手段之一。
真正决定一个系统健壮性的,是你对业务的理解、架构的设计、监控体系的完善程度,以及团队协同的能力。
引入分布式事务后,我们要做的还有很多,比如:
- 更完善的自动化测试;
- 更细致的异常上报机制;
- 更智能的告警规则;
- 更轻量的事务拆分;
- 更强大的降级预案……
希望这篇来自真实项目的经验分享对你有所启发。如果你也在用 Seata 或者正在规划分布式事务架构,欢迎留言一起交流,我们一起踩坑、填坑、升级打怪!
📌 关注公众号【TechGrow】,获取更多干货文章和技术成长笔记。

评论 0