面试考察点

  • 是否先明确一致性强度、业务边界和失败模型。
  • 能否比较 2PC、TCC、Saga 与可靠消息。
  • 是否把幂等、补偿、重试和对账纳入方案。

核心答案

分布式事务没有通用最优解。强一致且参与者支持协议时可评估 2PC;业务可提供预留、确认、取消接口时可用 TCC;长流程适合 Saga 补偿;可接受最终一致的事件驱动业务常用本地事务加 Outbox 或可靠消息。

首选仍是调整服务和数据边界,让一次事务在单库或单聚合内完成,避免不必要的跨服务原子性。

方案比较

2PC 语义直接但协调、阻塞和可用性成本高;TCC 控制精细却侵入业务且要处理空回滚、悬挂和幂等;Saga 每步提交本地事务,失败执行语义补偿,但补偿不一定能完全恢复外部世界。

先画清业务边界

订单服务:创建订单、锁定订单状态
库存服务:预占库存、确认扣减、释放库存
支付服务:支付授权、支付确认或退款
履约服务:创建发货任务

如果一个请求需要这些服务同时提交,先问能否把“订单 + 库存预占”归入一个聚合,或把支付和履约改成异步状态流。服务拆分后再强行追求跨服务 ACID,往往把网络、重试和不可控外部副作用引入核心事务。

方案对比细化

方案 一致性 可用性成本 业务侵入 适用场景
2PC/XA 较强 协调者故障可能阻塞 参与者少且都支持协议
TCC 业务可控 需要预留与补偿 资金、库存等可拆三阶段动作
Saga 最终一致 分步提交,可补偿 中高 长流程、每步可逆或可补偿
Outbox/可靠消息 最终一致 高可用、异步 事件驱动、允许延迟收敛

选择不能只看“强/弱一致”,还要看补偿是否可定义、参与者是否支持事务、用户能否看到中间状态和失败后是否需要人工介入。

TCC 的三个边界

Try 要预留资源且可重复调用,Confirm 真正提交,Cancel 释放预留。必须处理空回滚、悬挂和重复调用:空回滚是 Try 实际未成功却收到 Cancel;悬挂是旧 Try 晚于 Cancel 到达;重复 Confirm/Cancel 则来自重试和超时。每一步都要用事务 ID 和状态表做幂等,不能只靠调用方“应该只调用一次”。

Try -> 预占库存(status=TRY)
Confirm -> status=CONFIRMED
Cancel -> status=CANCELED

状态迁移要用条件更新,禁止 Confirm 后再被 Cancel 覆盖。TCC 的代码量和测试量很大,适合价值高且动作确实可拆的领域。

工程闭环

每个步骤使用全局事务或业务操作 ID,状态机只允许合法迁移。重试有退避与上限,失败进入人工或自动补偿队列;对账从事实源重新计算差异并支持可追溯修复。

Saga 与补偿设计

Saga 每一步本地提交并记录状态,失败后按逆序执行补偿。补偿不是数据库 rollback:已发出的短信、已扣的第三方款项、已送出的商品可能只能退款、重发或人工处理。补偿接口也要幂等、可重试、可观测,并记录“补偿失败待处理”而不是无限阻塞主流程。

Outbox 的故障时序

业务表和 outbox 事件在同一本地事务提交;投递器读未发送事件发布消息,收到确认后标记 sent。发布超时可能导致消息已发但状态未标记,下一次会重复发,因此消费者必须幂等。Outbox 表需要索引、分批清理、分区和积压监控,不能让事件永久堆积拖慢主库。

对账和状态机

每个跨服务操作都有全局 operation_id,状态只允许合法迁移:CREATED -> RESERVED -> PAID -> COMPLETED,失败进入 COMPENSATING/FAILED。定时对账从事实源或各服务状态重算差异,生成可追踪修复命令。没有状态机和对账的“最终一致”只是把错误藏到日志里。

常见误区

“最终一致”不是不做保证,而是保证在明确条件和时间内收敛。补偿也不是数据库回滚,例如已发送短信无法撤回,只能记录并采取后续业务动作。

高频追问与参考回答

追问:数据库成功、消息发送失败怎么办?

在同一本地事务中写业务数据和 Outbox 事件,后台可靠投递并标记状态;消费者幂等,投递失败可重试和对账。

追问:TCC 的 Cancel 一定会执行吗?

不能假设。协调器、网络和服务都可能故障,Cancel 也需要可靠重试、超时告警和人工补偿;Try 预留必须有过期释放机制。

追问:为什么不把所有服务加入 XA?

跨服务锁和协调会降低可用性、吞吐和故障恢复能力,且外部系统不支持 XA。只有参与者少、事务短、强一致收益明显时才评估。

追问:最终一致如何向用户解释?

用明确状态表达“处理中/待确认/失败可重试”,提供查询和补偿入口,不把尚未完成的异步流程伪装成成功。

总结

选型由一致性、持续时间、参与者能力和失败补偿决定,可靠实现必然包含状态机、幂等、重试、观测与对账。

机制全景图

下面把「分布式事务有哪些解决方案?」从输入到结果压缩成一条可复述的主链路。面试时先用图建立全局坐标,再进入局部实现,能避免只背零散结论。

flowchart LR
    A["定义跨服务不变量"]
    A --> B["本地事务落状态与事件"]
    B --> C["异步驱动后续步骤"]
    C --> D["失败补偿或重试"]
    D --> E["对账确认最终收敛"]

完整链路:从输入到结果

沿着「定义跨服务不变量 → 本地事务落状态与事件 → 异步驱动后续步骤 → 失败补偿或重试 → 对账确认最终收敛」观察输入、状态与输出,下面每个阶段都对应一个可以在源码、日志或系统表中验证的位置。

1. 定义跨服务不变量

先识别必须原子的不变量与允许的中间状态,避免一开始就要求所有服务强一致。

2. 本地事务落状态与事件

每个服务只在自己的数据库完成本地事务,Outbox 可把状态与待发布事件原子写入。

3. 异步驱动后续步骤

Saga 事件/命令驱动后续参与者,步骤必须幂等并记录全局事务与本地状态。

4. 失败补偿或重试

失败时可重试可恢复错误,业务拒绝则执行补偿;补偿是新业务动作,不是时间倒流。

5. 对账确认最终收敛

对账按全局事务 ID 比较各参与者状态,修复长期卡住或人工处理不可自动补偿的差异。

源码与实现定位

入口 阅读重点
transaction/outbox 状态表 全局步骤证据
补偿流水 UNIQUE(tx_id,step) 幂等补偿

源码或系统表应按上表顺序追踪:先确认入口实际走到哪条路径,再用运行时数据验证,而不是仅凭类名或配置推测。

参数配置与可复现实验

BEGIN; UPDATE orders ...; INSERT INTO outbox(tx_id,event) ...; COMMIT;

每个步骤前后故障注入,验证重试、补偿、悬挂和对账终态。

验证步骤与预期结果

1. 固定输入和基线

先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「状态超时数」为主基线,记录值应满足「记录活动/稳态基线」;同时保存 各状态停留时长、事件投递重试,使后续变化能够回到同一时间轴比较。

2. 从实现入口确认路径

在「transaction/outbox 状态表」确认请求确实进入「全局步骤证据」对应的实现,再沿「补偿流水 UNIQUE(tx_id,step)」观察「幂等补偿」。如果入口路径都未命中,就不应继续调整下游参数,而应先检查调用条件、版本或路由是否与假设一致。

3. 注入本文特有的失败模式

优先复现「补偿操作无幂等导致重复退款」,并把单一变量逐级放大,直到「状态超时数」越过「超过容量或 SLO」。随后再分别验证「只发 MQ 不解决本地提交与发送窗口」和「全局状态机没有超时终态」,三类故障分开执行,避免多个变量同时变化而无法归因。

4. 执行止损和根因修复

第一轮只应用「每步持久状态机」,确认它能控制影响范围;第二轮应用「补偿也幂等」,验证核心链路恢复;最后落实「超时扫描+人工终态」,消除同类问题再次出现的条件。每一步都保留变更前后数据,不用“感觉变快了”替代测量。

5. 通过退出条件

实验只有同时满足三项才算通过:「状态超时数」回到「记录活动/稳态基线」、「端到端 P99」回到「小于预算」、「状态差异」回到「0」,并且业务结果差异为零。若性能恢复但结果不一致,仍应视为失败;若指标恢复后很快再次越线,则说明只完成了临时止损,没有消除根因。

量化基线

指标 样例基线/口径 风险线 结论
状态超时数 记录活动/稳态基线 超过容量或 SLO 触发降级
端到端 P99 小于预算 突破预算 停止扩量
状态差异 0 任意非零 补偿并对账

这些数值是实验口径或示例告警线,不是可复制到所有系统的固定答案;上线阈值应由本系统稳态、峰值和故障演练共同确定。

事故复盘:下单成功但优惠券一直未返还

订单取消事件发送失败,券服务没有收到补偿。订单状态正确但全局事务未闭环。通过事务 Outbox、补偿状态机和超时扫描重发后,失败窗口可恢复。

失败模式 首要证据 第一处置动作
补偿操作无幂等导致重复退款 各状态停留时长 每步持久状态机
只发 MQ 不解决本地提交与发送窗口 事件投递重试 补偿也幂等
全局状态机没有超时终态 补偿成功率 超时扫描+人工终态

发布与回滚检查点

  • 发布前:确认「transaction/outbox 状态表」对应实现和上述配置在目标版本仍然有效,并保存「状态超时数」基线。
  • 灰度中:同时观察 各状态停留时长、事件投递重试、补偿成功率;任一指标越过表中风险线,就停止继续扩量。
  • 回滚时:先执行「每步持久状态机」控制影响,再回退代码或参数;涉及持久状态时必须额外核对结果差异。
  • 发布后:至少覆盖一个完整峰值周期,确认「补偿操作无幂等导致重复退款」没有再次出现,才关闭变更观察窗口。

方案对比与选型

方案 更适合的场景 主要收益 代价与边界
2PC/XA 参与资源少且强一致优先 统一提交语义 阻塞、协调器和跨服务可用性成本
Saga 长事务、业务可补偿 服务自治、可用性高 中间状态与补偿复杂
TCC 资源可显式 Try/Confirm/Cancel 预留清晰、可控 接口侵入大、悬挂空回滚治理

选型至少带上 峰值流量、数据规模、依赖容量、一致性目标和故障预算,并用上面的量化基线验证;未知数据应明确为待测假设。

设计边界与工程取舍

分布式事务的核心是把部分失败变成可记录、可重试、可补偿和可审计的状态;协议选择取决于不变量与业务可补偿性。

工程落地遵循:先定义 SLO 与不变量,再通过限流、隔离、异步和补偿保护核心链路。回答时直接引用「transaction/outbox 状态表」、配置实验和事故数据,比复述固定模板更有说服力。