分布式事务方案#
一句话答案#
强一致用 2PC/XA(性能差),最终一致用 TCC / SAGA / 本地消息表 / 事务消息,根据业务场景选择:资金用 TCC,普通业务用事务消息,长链路用 SAGA。
核心要点
方案全景对比#
| 方案 | 一致性 | 性能 | 代码侵入 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强 | 低(同步阻塞) | 低 | 传统数据库跨库事务 |
| TCC | 最终 | 高 | 极高(3 个方法) | 资金核心(扣款/转账) |
| SAGA | 最终 | 高 | 中 | 长链路跨多服务 |
| 本地消息表 | 最终 | 高 | 中 | 简单异步通知 |
| 事务消息 | 最终 | 高 | 低(推荐) | 普通业务解耦 |
各方案详解#
2PC / XA#
协调者 → 所有参与者: Prepare(预提交)
├── 全部 OK → 协调者发 Commit
└── 任一失败 → 协调者发 Rollbackplaintext代价:
- 同步阻塞:Prepare 到 Commit 之间所有参与者持有锁
- 协调者单点:协调者宕机 → 参与者一直持锁
- 性能上限:通常 < 100 QPS(取决于锁等待时间)
适用: 遗留系统已有 XA 驱动、不想改代码、能接受性能损失。
TCC(Try-Confirm-Cancel)#
Try: 冻结资源(如冻结 100 元,非直接扣除)
Confirm: 确认执行(将冻结的 100 元真正扣除)
Cancel: 取消回滚(将冻结的 100 元释放回可用余额)plaintext核心要求:
- 每个服务实现三个方法(Try / Confirm / Cancel)
- Confirm 和 Cancel 必须幂等(可能被重复调用)
- 需要事务协调框架(如 Seata TCC 模式)
代价: 代码侵入极高——每个服务的业务逻辑要拆成三步,开发量翻倍。
适用: 资金交易(扣款、转账)——值得为强一致付出高开发成本。
SAGA#
正向操作链:T1 → T2 → T3 → ... → Tn
如果 Ti 失败:执行补偿链 Ci-1 → ... → C2 → C1(逆序回滚)plaintext两种编排方式:
| 方式 | 特点 |
|---|---|
| 编排式(Choreography) | 每个服务监听事件触发下一步,去中心化 |
| 协调式(Orchestration) | 中央协调器控制流程,集中管理 |
编排式适合简单链路(3-4 步),协调式适合复杂长链路。
代价: 补偿逻辑可能复杂(如”已发短信”如何补偿?→ 不能撤回,只能发补偿短信)。
适用: 长链路跨多服务(下单→扣库存→扣积分→发短信→记日志),每步都可能失败。
本地消息表#
1. 业务操作和写消息表在同一个本地事务中
BEGIN;
INSERT INTO orders ...;
INSERT INTO message_table (event, status) VALUES ('order_created', 'pending');
COMMIT;
2. 定时任务轮询 message_table → 发送到 MQ
3. 消费方处理完成 → 回调更新 status = 'done'
4. 发送失败 → 定时任务重试plaintext代价: 需要额外的消息表 + 定时轮询任务 + 消费方幂等。
适用: 没有支持事务消息的 MQ(如 Kafka 早期版本)、简单异步通知场景。
事务消息(RocketMQ)#
1. Producer 发送半消息(Half Message)→ MQ 暂存,Consumer 不可见
2. Producer 执行本地事务
├── 成功 → 发送 Commit → MQ 投递给 Consumer
└── 失败 → 发送 Rollback → MQ 丢弃半消息
3. 如果 Producer 宕机没发 Commit/Rollback
→ MQ 定时回查 Producer 的本地事务状态
→ 根据查询结果 Commit 或 Rollbackplaintext优势: 不需要额外的消息表和定时轮询,MQ 原生支持。
Kafka vs RocketMQ 事务消息:
| 维度 | RocketMQ | Kafka |
|---|---|---|
| 机制 | 半消息 + 事务回查 | 事务 Producer + 事务协调器 |
| 适用 | 通用事务消息 | Exactly-Once 语义(流处理) |
| 推荐 | 业务事务场景首选 | 流处理场景 |
选型决策矩阵#
问自己三个问题:
1. 是否需要强一致?
├── 是(资金/账户)→ TCC
└── 否(最终一致可接受)→ 继续问
2. 链路有多长?
├── 2-3 个服务 → 事务消息(低侵入)
└── 4+ 个服务 → SAGA(补偿链)
3. 有没有成熟的 MQ?
├── 有 RocketMQ → 事务消息
├── 只有 Kafka → 本地消息表 / Kafka 事务
└── 没有 MQ → 本地消息表 + 定时任务plaintext用订单场景串联#
下单 → 扣库存 → 扣余额 → 发通知
| 环节 | 方案 | 原因 |
|---|---|---|
| 下单 + 扣库存 | 事务消息 | 库存最终一致即可,不需要强一致 |
| 扣余额 | TCC | 资金操作需要强一致,Try 冻结→Confirm 扣除 |
| 发通知 | 本地消息表 / MQ | 通知是旁路操作,失败重试即可 |
所有环节都需要幂等 + 对账机制兜底。
面试回答(2分钟版)
分布式事务我按一致性要求选方案。强一致场景如资金交易,用 TCC——Try 冻结资源、Confirm 真正扣除、Cancel 释放,三个方法都幂等,代价是代码侵入高但值得。普通业务最终一致就够了,首选事务消息——RocketMQ 的半消息机制,Producer 先发半消息,本地事务成功再 Commit,失败 Rollback,Producer 宕机 MQ 会回查。长链路跨多服务用 SAGA,正向操作链 + 逆向补偿链。本地消息表是事务消息的替代,用于没有 RocketMQ 的场景。2PC 性能太差,通常不用于互联网场景。核心原则:大多数业务最终一致就够了,别为了强一致付出过高代价。所有方案都需要幂等 + 对账兜底。
追问与易错
追问方向:
- “你项目用了哪种?”→ 结合项目讲,说清选型理由
- “TCC 的 Cancel 失败了怎么办?”→ 重试 + 告警 + 人工介入,Cancel 必须幂等
- “事务消息和本地消息表的区别?”→ 事务消息不需要额外表和轮询,MQ 原生支持回查
- “为什么不用 2PC?”→ 同步阻塞、协调者单点、性能差,互联网场景几乎不用
易错点:
- ❌ “分布式事务一定要强一致”——大部分业务最终一致就够
- ❌ 不考虑补偿/对账——任何方案都可能有异常,对账是最后防线
- ❌ “用了 SAGA 就不用幂等”——SAGA 的补偿操作也需要幂等
- ❌ 混淆 TCC 和 2PC——TCC 是业务层面的,2PC 是资源层面的