分布式事务解决方案:一次真实项目中的踩坑与重构

LeetCode逃兵
2025-06-20 09:08
阅读 675

大家好,我是阿磊,一名后端工程师,在过去的几年里参与过多个中大型系统的开发和重构。今天想跟大家分享一个我亲身经历的关于分布式事务的实际案例。这篇文章不会是干巴巴的技术原理说明,而是一次真实的踩坑、反思和优化过程。

如果你正在处理微服务架构下的订单系统、支付系统或者金融类交易系统,那么你一定会遇到一个绕不开的问题:如何在跨服务调用的场景下保证数据一致性?

下面我将带你走进我们当时项目的背景、踩过的坑、以及后来采用的有效解决方案。


一、项目背景:从单体到微服务的过渡

一、项目背景:从单体到微服务的过渡

这个故事要从一个电商平台的重构开始说起。

我们原来的系统是一个典型的“单体应用”,所有业务逻辑都在一个应用里面完成,比如下单、支付、库存扣减等流程全部在一个数据库事务里执行。虽然结构简单、性能尚可,但在业务快速发展的背景下,单体架构带来的问题是越来越明显:

  • 模块间耦合严重:改一个小功能可能影响整个系统
  • 部署困难:每次发布都要停机或灰度切换
  • 容量瓶颈明显:高峰期时数据库压力巨大

于是我们决定进行服务化改造,按照领域模型拆分出用户中心、商品中心、库存中心、订单中心、支付中心等多个服务模块,每个服务都有独立的数据库。

一切听起来都很美好,直到我们遇到了——


二、问题出现:下单失败导致的数据不一致

二、问题出现:下单失败导致的数据不一致

在服务拆分之后的第一个完整迭代周期中,我们上线了新的订单创建流程。

新流程是这样的:

  1. 用户发起下单请求
  2. 订单服务生成订单记录(状态为“待支付”)
  3. 调用商品中心接口校验库存是否足够
  4. 如果库存足够,则调用库存服务锁定库存(预扣减)
  5. 最后引导用户进入支付页面进行支付,支付成功后再真正扣减库存并更新订单状态

看似流程清晰,但上线没几天就出了问题:

某些用户提交订单后,库存显示被锁定了,但是订单状态却始终为“待支付”。
更糟的是,有些订单根本没有生成,但库存已经被锁死,无法再次销售。

这显然是典型的数据一致性问题。我们在不同服务之间通过HTTP请求进行交互,没有使用任何事务机制来保障整体操作的成功或失败。

更具体地说,这个问题涉及以下几点风险:

  • 接口调用失败或超时导致流程中断
  • 多个服务之间的状态同步延迟
  • 本地事务与远程调用无法原子性保证

也就是说,这就是一个标准的分布式事务场景


三、初次尝试:基于两阶段提交(2PC)的方案

三、初次尝试:基于两阶段提交(2PC)的方案

面对这个问题,我们的第一反应是寻找一个“官方”的分布式事务解决方案。于是团队里有同学提议使用两阶段提交(Two Phase Commit, 简称2PC),因为它是最经典的分布式事务协议之一。

我们参考了一些开源组件,最终选择了阿里开源的 Seata。Seata 提供了对 Spring Cloud 和 Dubbo 的集成支持,看起来能满足我们的需求。

当时的实现思路如下:

  1. 使用 Seata 的全局事务管理器(Transaction Manager)
  2. 在订单服务开启一个全局事务,标记为 @GlobalTransactional
  3. 下游服务如商品中心、库存中心也集成 Seata 客户端,并注册进 TC(Transaction Coordinator)
  4. 所有涉及到数据修改的操作都走 Seata 的 AT 模式,自动拦截 SQL 并做快照,实现事务回滚/提交

乍一看这套方案挺完美,但实际落地后却发现很多问题:

  • 性能开销大:引入 Seata 后,接口响应时间普遍上升了 20%+,特别是在高并发下单场景中表现尤为明显。
  • 网络依赖高:如果 TC 服务不稳定或者网络抖动,整个事务链都会卡住。
  • 复杂度陡增:需要额外维护 Seata 服务集群,监控告警配置麻烦,日志也不够直观。
  • 数据库兼容性问题:部分 MySQL 表由于用了非标准语句(如存储过程、自定义函数),导致 Seata 解析失败。

最让我记忆深刻的一次故障发生在双十一前夕的一次压力测试中:

我们模拟大量并发下单,结果 Seata 的 Transaction Coordinator 集群出现了脑裂现象,事务日志混乱,最终不得不临时降级到纯本地事务模式,导致当天部分订单数据异常,后续靠人工补账才解决。

这次教训让我们意识到:技术方案必须贴合业务场景,而不是为了追求“先进性”盲目引入新技术。


四、重新思考:基于 Saga 模式和本地消息表的折中方案

既然重剑无锋的 Seata 不太适合我们的业务场景,那怎么办?

我们决定换个思路:不是所有的业务都需要强一致性,能容忍一定程度的异步和补偿,就可以用 Saga 或者本地消息表这类柔性事务方式。

✅ 方案设计:本地消息表 + 定时补偿

我们最终采用了如下设计方案:

1. 关键点设计

  • 每个服务内部保留一张“本地事务消息表”,用于记录本次操作是否已经触发下游事务
  • 所有业务操作和插入事务消息在同一本地事务中完成
  • 异步消息队列拉取这些事务消息,推动后续流程继续执行
  • 若中间步骤失败,定时任务检测未完成的消息状态,触发补偿机制(如回滚库存)

2. 示例:下单流程的改进

以创建订单为例,整个流程改为:

  1. 订单服务启动本地事务

    • 插入订单记录
    • 写入一条本地事务消息(类型:已下单)
    • 本地事务提交
  2. 订单服务异步发送消息到 RabbitMQ / Kafka / RocketMQ(根据公司基础设施选择)

  3. 商品中心监听到消息,校验库存是否可用:

    • 若可用,返回确认;
    • 若不可用,发送“订单无效”事件,由订单服务清理该订单
  4. 库存服务监听到消息后,锁定库存,并写入自己的本地事务消息

  5. 支付服务完成后,再通知库存服务正式扣减库存

  6. 定时任务轮询未完成的订单消息,判断是否超时未支付,触发回滚库存等操作

这种方式的优点很明显:

  • 架构轻量,无需引入复杂的分布式事务框架
  • 降低对网络稳定性和第三方服务的强依赖
  • 异步化提升了整体吞吐能力

当然也有缺点:

  • 实现复杂,要自己处理各种幂等、消息重复消费、补偿逻辑等问题
  • 事务边界变模糊,需要更多监控机制确保最终一致性

不过对于我们的场景来说,这种最终一致性是完全可以接受的。


✅ 技术细节补充

在实际开发中,我们做了几个关键的优化:

📌 消息可靠性保证

  • 所有写入消息的操作必须和本地事务绑定
  • MQ 写入失败需要做重试机制,并限制最大重试次数
  • 消费端要做幂等控制(比如通过唯一 ID 做防重处理)

📌 数据库设计优化

  • 事务消息表字段包括:
    • 业务ID(订单ID)
    • 消息类型(下单、支付、取消等)
    • 当前状态(已发送、已处理、失败等)
    • 下一步目标服务
    • 重试次数
  • 所有相关字段加索引,以便定时任务高效扫描

📌 定时补偿任务

  • 每天凌晨跑一次全量数据稽核,确保所有事务最终收敛
  • 异常订单进入专门的“争议处理池”,后台人员介入修复

五、效果对比与收益总结

方案上线三个月后,我们统计了几个核心指标的变化:

指标 上线前 上线后 变化幅度
平均下单耗时 280ms 220ms ↓21%
下单失败率 3.1% 0.7% ↓77%
异常订单占比 1.2% 0.3% ↓75%
系统运维复杂度

而且最重要的是,我们在接下来的几次压测中再也没有出现之前那样的“大故障”。


六、经验总结与建议

作为一个亲历者,我想给正在探索分布式事务方案的朋友提几点建议:

💡 1. 明确业务对一致性的要求

不是所有场景都需要强一致性。你可以问自己这几个问题:

  • 出现短暂不一致是否可接受?
  • 是否可以通过补偿机制恢复?
  • 有没有合适的时机做批量修复?

如果是高频交易或者金融类系统,可以考虑 Seata + TCC 这类更严格的方案;如果是电商、物流等场景,Saga 模式更适合。

💡 2. 尽量保持本地事务优先

不要一开始就把分布式事务当作万能钥匙。尽可能把核心事务集中在本地完成,再用异步解耦的方式去推进下一步操作。

💡 3. 设计事务消息时要考虑可追溯

消息结构尽量丰富一些,比如加上上下游服务名、操作类型、当前状态、错误信息等字段,这样后期排查问题会更容易。

💡 4. 加强监控和报警机制

  • 对消息堆积、处理失败、重复处理的情况要有告警
  • 定期跑数据稽核任务,防止漏单、多扣等情况
  • 保留操作日志,方便追踪每一个事务的生命周期

💡 5. 组件选型因地制宜

比如我们之所以没有选 RocketMQ 而选择了 RabbitMQ,是因为公司在 RabbitMQ 上积累了丰富的运维经验,而且它更适用于小规模的、实时性强的场景。

另外一点很重要:不要轻易引入未经生产验证的技术方案。


七、写在最后

分布式事务是个老话题,也是个难题。但我相信,只要深入理解业务本质,结合实际情况灵活选用方案,就能找到适合自己系统的一致性路径。

这篇文章讲的是我的一次实战经历,其中有很多踩坑的过程,也有一些事后反思。希望对你有所启发。

如果你也在做类似的系统架构设计,欢迎留言交流,我们可以一起探讨更好的做法。

共勉 😊


如需获取文中提到的设计文档模板或代码片段,请留言联系,我会整理分享。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝