分布式事务的几种落地模式
单体应用里,一个 @Transactional 解决所有一致性问题。拆成微服务后,一个下单流程要跨订单服务、库存服务、账户服务——本地事务不管用了,得引入分布式事务。这篇文章梳理几种主流落地方案,以及它们各自适合的边界。
先明确:一致性有强弱之分
讨论方案前必须区分两种一致性:
- 强一致性:任何时刻所有节点看到的数据都一致。代价是性能,通常需要锁或同步等待。
- 最终一致性:允许短暂不一致,但最终会收敛到一致状态。代价是业务上要处理中间态。
大多数互联网业务(下单、支付、发货)其实只需要最终一致——用户能接受「下单后几秒内库存才扣减」,但不能接受「多扣了钱」。只有金融核心账务这类场景才必须强一致。
方案一:2PC / XA(强一致,但很重)
两阶段提交。协调者先问所有参与者「能提交吗」(prepare),全票通过后再通知「提交」(commit)。
优点:数据库层面原生支持,业务代码几乎不用改。
缺点:同步阻塞(prepare 阶段所有参与者都要持锁等待);协调者单点;性能差(吞吐通常只有单库的 1/5~1/10)。
适用:跨库但同构的场景,并发量不大,比如内部管理系统的批处理。**互联网高并发场景基本不用**。
方案二:TCC(Try-Confirm-Cancel)
业务层面的两阶段。每个参与方提供三个接口:
- Try:预留资源(冻结库存、冻结金额),不真正扣减
- Confirm:确认执行(真正扣减)
- Cancel:回滚(释放预留)
// 库存服务的 Try
boolean tryDeduct(String orderId, int count) {
// 冻结库存:available -= count, frozen += count
return stockMapper.freeze(orderId, count) > 0;
}
// Confirm:frozen -= count
// Cancel:available += count, frozen -= count
优点:性能好(Try 阶段不长时间持锁);可控性强(业务自己定义预留和回滚逻辑)。
缺点:**改造成本最高**——每个参与方都要实现三个接口,还要处理空回滚、幂等、悬挂(Cancel 先于 Try 到达)这三大难题。
适用:资金类、库存类等强一致要求且并发高的核心场景。支付宝早期就是这么做的。
方案三:Saga(长事务拆分)
把一个大事务拆成一串本地事务,每个都有对应的补偿操作。任何一步失败,反向执行已完成步骤的补偿。
下单 → 扣库存 → 扣余额 → 创建物流单
失败则 ← 补偿:加回库存
← 补偿:加回余额
优点:无锁,性能好;天然适合长流程(比如跨天的审批流)。
缺点:**没有隔离性**——中间态对外可见。比如扣了库存但还没扣余额,这时用户查库存会看到已扣。需要业务层面设计「状态机」来屏蔽中间态。
适用:流程长、参与方多、能接受中间态的业务,如订单履约、旅行预订(机票+酒店+租车)。
方案四:本地消息表 / 事务消息(最终一致,最常用)
核心思路:**把「业务操作」和「发消息」放在同一个本地事务里**,保证两者原子性。然后异步投递消息,下游消费。
BEGIN;
INSERT INTO orders (...) VALUES (...);
INSERT INTO message_outbox (topic, payload, status) VALUES ('stock.deduct', ..., 'PENDING');
COMMIT;
-- 独立线程扫描 outbox,投递 MQ,成功后标记 SENT
优点:实现简单,不侵入业务代码;可靠性高(消息和业务同库同事务)。
缺点:需要额外的 outbox 表和投递线程;下游要保证幂等(消息可能重复)。
适用:绝大多数业务场景的默认选择。**如果只有一个方案可选,选这个。**
RocketMQ 的事务消息是这个思路的产品化实现,省掉了自己写 outbox 的功夫。
方案五:最大努力通知
最简单粗暴:发起方执行业务后,尽力通知下游,失败就定时重试(比如重试 5 次,间隔递增)。下游如果一直收不到,靠对账来兜底。
优点:实现成本极低,不需要 MQ,不需要事务框架。
缺点:不保证一定送达,必须有对账机制兜底。
适用:对实时性要求不高、能接受人工介入的场景,比如第三方支付回调、积分发放。
方案对比
| 方案 | 一致性 | 性能 | 改造成本 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强 | 差 | 低 | 低并发内部系统 |
| TCC | 强 | 好 | 高 | 资金/库存核心链路 |
| Saga | 最终 | 好 | 中 | 长流程、多参与方 |
| 本地消息表 | 最终 | 好 | 低 | 大多数业务(推荐) |
| 最大努力通知 | 最终 | 好 | 极低 | 非核心、可对账 |
三个实战经验
1. 能不分布式就不分布式。 最可靠的分布式事务方案是「不要分布式事务」。评估一下:这两个服务真的需要拆开吗?能不能合并?很多分布式一致性问题,本质是拆错了服务边界。
2. 优先选最终一致 + 对账。 银行转账也不需要毫秒级一致,T+0 对账就能兜住所有异常。设计时先问「这个业务能接受多长时间的中间态」,往往答案是「几分钟都行」,那就没必要上 TCC。
3. 幂等是前提,不是可选项。 所有最终一致方案都依赖消息重投或补偿重试,如果下游不幂等,重试就是灾难。**先做好幂等,再谈分布式事务。**
小结
选型的核心判断是三个问题:需要多强的一致性?并发量多大?能接受多大的改造成本?
实际项目里的典型组合:核心交易链路用 TCC(资金)+ 本地消息表(库存扣减通知)+ 每日对账兜底。其余非核心流程一律最终一致 + 最大努力通知。
- 点赞
- 收藏
- 关注作者
评论(0)