分布式事务解决方案:我在项目中踩过的坑和学到的教训

赵强♪
2025-06-24 21:44
阅读 511

背景介绍:为什么我不得不面对分布式事务?

背景介绍:为什么我不得不面对分布式事务?

三年前,我加入了一个正在搭建电商平台的创业团队。平台初期采用的是单体架构,随着业务增长,我们逐步拆分服务,引入了用户服务、订单服务、库存服务、支付服务等微服务模块。一开始一切看起来都很顺利,但很快我们就遇到了一个绕不开的问题——跨服务的数据一致性

举个最简单的例子:

用户下单后,我们需要从订单服务创建订单、从库存服务扣减库存、可能还要调用优惠券服务扣减优惠券,甚至涉及第三方支付系统。这些操作必须要么全部成功,要么全部失败,否则就会出现数据不一致的情况,比如订单建好了,库存却没扣,用户可能就白赚一个商品。

这就牵扯到了一个很经典的技术问题:分布式事务。这个问题在我职业生涯里几乎每做一个中大型系统都会遇到一次,而这次,是它逼我彻底去研究并实践了一些常见的解决方案。

遇到的问题:业务越来越复杂,本地事务扛不住了

遇到的问题:业务越来越复杂,本地事务扛不住了

我们最初的处理方式非常“简单粗暴”:在订单创建时,先创建订单记录,然后直接调用库存服务的接口来减少库存。如果库存服务调用失败,我们就把订单状态标记为“异常”,后续再由人工介入处理。

可现实并不这么理想,因为:

  1. 服务之间调用频繁失败,网络不稳定、超时重试导致数据重复;
  2. 用户投诉越来越多,库存被错误地锁定或未释放;
  3. 人工处理成本越来越高,运维压力山大;
  4. 高峰期并发订单暴涨,系统响应变慢甚至崩溃。

这时候我们才意识到:不能继续靠“人肉补偿”来兜底了,需要引入正式的分布式事务机制,来保证多服务间的操作一致性。

解决方案选择:我们尝试了哪些技术?

数据流转过程-1

解决方案选择:我们尝试了哪些技术?

我们调研了很多方案,包括两阶段提交(2PC)、TCC、Saga 模式、消息队列+最终一致性等等。每个方案都有其适用场景,也各有利弊。我们在几个关键点上做了对比:

方案 优点 缺点 我们是否采用
2PC/XA 强一致性,支持ACID 性能差、容易阻塞、依赖中心协调者
TCC 灵活、性能好、适合业务补偿 开发工作量大、需要设计 Cancel 和 Confirm 接口
Saga 流程清晰、异步执行快 补偿逻辑复杂、可能出现中间状态 部分场景使用
MQ + 最终一致 实现简单、解耦彻底 数据会短暂不一致 常用于非关键路径

最终,我们在订单核心链路选择了TCC模式,而在对一致性要求没那么高的场景下,比如通知、日志类操作,使用了MQ + 最终一致性模型。

为什么我们选TCC?

TCC 的核心思想是将整个分布式事务分为三个步骤:

  • Try:尝试执行资源预留
  • Confirm:确认执行操作(正常流程)
  • Cancel:回滚操作(异常流程)

虽然开发成本较高,但它具有以下优势:

  • 不依赖数据库的 XA 事务,适用于多种存储类型;
  • 对服务侵入性可控,只要定义好接口即可;
  • 可以与异步处理结合使用;
  • 更容易满足高并发场景下的性能要求。

实践细节:我们的TCC实现长什么样?

我们在项目中使用了开源框架 Seata 来协助实现 TCC 事务管理。Seata 提供了较为完整的分布式事务控制能力,尤其是在全局事务协调方面做得很好。

下面是一个简化版的核心流程图:

[Order Service]        [Inventory Service]
     |                      |
Create Order (Local)    |
     |--------------------> Call Try on Inventory
     |<--------------------
     |                    Inventory Reserved OK
     |
Call Try on Coupon      |
     |-------------------->
     |<--------------------
Coupon Locked
     |
Commit TCC Transaction
     |
Confirm: Inventory & Coupon
     |
Finish Order

关键代码示例

这里展示一个典型的 Try、Confirm、Cancel 接口定义:

@TwoPhaseBusinessAction(name = "deductStock")
public boolean deductStock(BusinessActionContext ctx);

@Commit
public boolean commitDeductStock(BusinessActionContext ctx);

@Rollback
public boolean rollbackDeductStock(BusinessActionContext ctx);

每个服务都要在这三个阶段做对应的操作:

  • Try 方法完成库存预占,不会真正扣除库存;
  • commit 时正式扣减;
  • 如果发生异常或全局事务回滚,则触发 rollback 操作,恢复之前预占的库存。

Seata配置示例(部分):

seata:
  enabled: true
  application-id: inventory-service
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: file

API接口文档-2

Seata 通过全局事务 ID (XID) 将各个分支事务串联起来,在每个服务调用时自动传播这个事务上下文。

踩坑经验:不是所有的路都一帆风顺

尽管 Seata 功能强大,但在真实生产环境中还是遇到了一些挑战。

坑1:幂等问题没有处理好,导致数据重复

由于网络原因或者服务宕机,某些请求可能会被多次提交。如果没有做好幂等性处理,在 Confirm 或 Cancel 阶段,可能导致库存被多扣或者多释放。

解决方法是在每个事务动作中加入唯一业务流水号(例如订单ID + 资源ID),并在 DB 中记录已处理的动作。

坑2:Cancel 阶段异常无法自动重试

有一次,库存服务在执行 Cancel 操作时报错,但是事务已经标记为 Rollback,这时候并没有自动重试机制,导致库存没有正确释放。

后来我们在 Cancel 阶段加入了异步补偿机制,失败时将任务丢进延迟队列中不断重试,直到成功。

坑3:Seata 自带的 AT 模式不适合我们场景

AT 模式本质上是对数据库加锁并记录镜像表,但我们在使用 MongoDB 做库存记录时发现,AT 模式对其支持并不友好。

所以最后果断切换成完全自定义的 TCC 模式,虽然开发复杂度提高了,但对各种存储类型的兼容性和灵活性更好。

实际效果:上线后的表现如何?

自从我们全面接入 Seata 并完善了 TCC 的补偿机制后,系统的稳定性大大提升:

  • 订单和库存不一致的问题基本消失;
  • 人工干预的需求减少 80%;
  • 即使在流量高峰时段,也能稳定支撑起每秒上千笔订单;
  • 后续新业务接入 TCC 也有了统一的标准接口模板,开发效率反而提升了。

当然也有代价,主要是两个:

  • 开发同学需要额外编写 Confirm/Cancel 逻辑,学习成本高一点;
  • TCC 本身有一定的性能损耗,特别是在 Confirm/Cacel 执行次数较多时。

但总体来看,收益远大于付出。

给读者的一些建议

如果你现在正面临着类似的分布式事务问题,我可以根据我的实际经验给你几点建议:

1. 不要一开始就追求“强一致性”

强一致性意味着更高的开发维护成本。对于很多业务来说,比如消息通知、积分更新、日志写入等,“最终一致性”加上合理的补偿机制就已经足够,没必要为了“实时准确”而去牺牲性能。

2. TCC 适用于大多数电商/交易类场景,但要做好补偿逻辑设计

TCC 很适合有明确资源预留、可以分离 Try 和 Confirm 的场景,比如库存、优惠券、会员权益等。但如果业务太复杂,或者数据流向不清晰,建议慎重考虑。

3. 引入 TCC 框架之前,一定要理解它的运行机制

Seata 是个不错的工具,但别盲目依赖。要弄清楚它是怎么传播 XID、怎么做事务注册和提交的。否则遇到问题你根本不知道该从哪里排查。

4. 失败补偿比成功更重要

很多人只关注流程顺利跑通的时候,但实际上,90% 的工作都在于处理异常情况。Cancel 操作是否能执行、执行失败如何补偿、会不会重复执行?这些问题都需要考虑周全。

5. 不要怕写冗余代码,但要封装得优雅

TCC 要求你为每一个服务写 Confirm 和 Cancel 接口,看起来有点繁琐,但如果你用注解+适配器等方式合理封装,这部分工作是可以复用的。

小结:技术是手段,目标是解决问题

说到底,分布式事务不是一个技术难题,而是一个业务需求落地的问题。不同的业务场景决定了我们应该选择什么样的事务模型。在我参与的多个项目中,我也尝试过其他方案,比如基于 Kafka 的事件驱动+最终一致性、基于 Redis 的临时状态记录等。

但只有当你亲自经历过线上故障、数据不一致带来的痛苦,才会真正理解分布式事务的重要性和复杂性。

希望这篇来自一线实战经验的文章,能够帮你在面对分布式事务问题时少走弯路。如果你有任何疑问,欢迎留言交流,我们一起探讨更优解。


作者简介
一名有着5年工作经验的后端工程师,热爱分享实战经验,专注分布式系统和高并发架构设计。目前在某头部电商平台负责交易平台核心模块的研发与优化。

评论 0

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