面试知识库
极高 困难

分布式事务方案#

一句话答案#

强一致用 2PC/XA(性能差),最终一致用 TCC / SAGA / 本地消息表 / 事务消息,根据业务场景选择:资金用 TCC(资源预留、隔离性好,但仍是最终一致),普通业务用事务消息,长链路用 SAGA。

核心要点

方案全景对比#

方案一致性性能代码侵入适用场景
2PC/XA强低(同步阻塞)低传统数据库跨库事务
TCC最终高极高(3 个方法)资金核心(扣款/转账)
SAGA最终高中长链路跨多服务
本地消息表最终高中简单异步通知
事务消息最终高低(推荐)普通业务解耦

各方案详解#

2PC / XA#

协调者 → 所有参与者: Prepare(预提交)
  ├── 全部 OK → 协调者发 Commit
  └── 任一失败 → 协调者发 Rollback
plaintext

代价:

  • 同步阻塞:Prepare 到 Commit 之间所有参与者持有锁
  • 协调者单点:协调者宕机 → 参与者一直持锁
  • 性能:吞吐明显低于本地事务,取决于锁持有时间和多轮网络 RTT(没有固定的 QPS 上限)

适用: 遗留系统已有 XA 驱动、不想改代码、能接受性能损失。

TCC(Try-Confirm-Cancel)#

Try:     冻结资源(如冻结 100 元,非直接扣除)
Confirm: 确认执行(将冻结的 100 元真正扣除)
Cancel:  取消回滚(将冻结的 100 元释放回可用余额)
plaintext

核心要求:

  • 每个服务实现三个方法(Try / Confirm / Cancel)
  • Confirm 和 Cancel 必须幂等(可能被重复调用)
  • 需要事务协调框架(如 Seata TCC 模式)

代价: 代码侵入极高——每个服务的业务逻辑要拆成三步,开发量翻倍。

适用: 资金交易(扣款、转账)——Try 阶段冻结资源,业务上隔离性好,值得付出高开发成本;注意 TCC 仍属最终一致,Confirm/Cancel 之间存在中间态。

SAGA#

正向操作链:T1 → T2 → T3 → ... → Tn
如果 Ti 失败:执行补偿链 Ci-1 → ... → C2 → C1(逆序回滚)
plaintext

两种编排方式:

方式特点
协同式(Choreography)每个服务监听事件触发下一步,去中心化
编排式(Orchestration)中央编排器(如 Seata Saga 状态机)控制流程,集中管理

协同式适合简单链路(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——Kafka 事务只保证 Kafka 内部多分区写/消费位点提交的原子性,不能和业务库本地事务绑定)、简单异步通知场景。

事务消息(RocketMQ)#

1. Producer 发送半消息(Half Message)→ MQ 暂存,Consumer 不可见
2. Producer 执行本地事务
   ├── 成功 → 发送 Commit → MQ 投递给 Consumer
   └── 失败 → 发送 Rollback → MQ 丢弃半消息
3. 如果 Producer 宕机没发 Commit/Rollback
   → MQ 定时回查 Producer 的本地事务状态
   → 根据查询结果 Commit 或 Rollback
plaintext

优势: 不需要额外的消息表和定时轮询,MQ 原生支持。

Kafka vs RocketMQ 事务消息:

维度RocketMQKafka
机制半消息 + 事务回查事务 Producer + 事务协调器
适用通用事务消息Exactly-Once 语义(流处理)
推荐业务事务场景首选流处理场景

选型决策矩阵#

问自己三个问题:
1. 是否需要强一致?
   ├── 是(资金/账户)→ 能放同库就用本地事务;跨库用 XA;跨服务用 TCC(资源预留,严格说仍是最终一致)
   └── 否(最终一致可接受)→ 继续问
   
2. 链路有多长?
   ├── 2-3 个服务 → 事务消息(低侵入)
   └── 4+ 个服务 → SAGA(补偿链)
   
3. 有没有成熟的 MQ?
   ├── 有 RocketMQ → 事务消息
   ├── 只有 Kafka → 本地消息表(Outbox)/ CDC 订阅 binlog 投递
   └── 没有 MQ → 本地消息表 + 定时任务
plaintext

用订单场景串联#

下单 → 扣库存 → 扣余额 → 发通知

环节方案原因
下单 + 扣库存事务消息库存最终一致即可,不需要强一致
扣余额TCC资金操作要求资源预留和隔离,Try 冻结→Confirm 扣除
发通知本地消息表 / MQ通知是旁路操作,失败重试即可

所有环节都需要幂等 + 对账机制兜底。

面试回答(2分钟版)

分布式事务我按一致性要求选方案。资金交易这类对隔离性要求高的场景用 TCC——Try 冻结资源、Confirm 真正扣除、Cancel 释放,三个方法都幂等,代价是代码侵入高但值得。普通业务最终一致就够了,首选事务消息——RocketMQ 的半消息机制,Producer 先发半消息,本地事务成功再 Commit,失败 Rollback,Producer 宕机 MQ 会回查。长链路跨多服务用 SAGA,正向操作链 + 逆向补偿链。本地消息表是事务消息的替代,用于没有 RocketMQ 的场景。2PC 性能太差,通常不用于互联网场景。核心原则:大多数业务最终一致就够了,别为了强一致付出过高代价。所有方案都需要幂等 + 对账兜底。

追问与易错

追问方向:

  • “实际选型怎么讲?”→ 按「一致性要求 → 链路长度 → 基础设施」三步说:资金扣减用 TCC 冻结再确认;下单后通知/发积分用 RocketMQ 事务消息;没有事务消息的 MQ 用本地消息表;再补一句异常处理(Cancel 重试、消息回查、消费幂等、对账)
  • “TCC 的 Cancel 失败了怎么办?”→ 重试 + 告警 + 人工介入,Cancel 必须幂等
  • “事务消息和本地消息表的区别?”→ 事务消息不需要额外表和轮询,MQ 原生支持回查
  • “为什么不用 2PC?”→ 同步阻塞、协调者单点、性能差,互联网场景几乎不用

易错点:

  • ❌ “分布式事务一定要强一致”——大部分业务最终一致就够
  • ❌ 不考虑补偿/对账——任何方案都可能有异常,对账是最后防线
  • ❌ “用了 SAGA 就不用幂等”——SAGA 的补偿操作也需要幂等
  • ❌ 混淆 TCC 和 2PC——TCC 是业务层面的,2PC 是资源层面的