第三章 分布式事务与一致性方案:高频问题、回答框架、公司侧重点

本章核心命题:在跨服务、跨数据库的支付链路中,如何保证数据最终一致?面试官重点关注你对 TCC、Saga、本地消息表、Seata AT 等方案的选型判断力、异常处理设计以及幂等与防悬挂的落地细节。
分布式事务选型 短事务+强一致 长事务+最终一致 TCC / Seata AT XA (极少用) Saga + 补偿 本地消息表 幂等 & 空回滚 防悬挂 ≤ 3个核心 正向 + 补偿幂等 可靠投递 + 去重 事务消息 + 回查 状态机驱动
面试加分点:引用“最大努力通知”与“可靠消息”的差异,以及支付宝、微信支付内部常用“TCC + 消息对账”的组合方案。

问答卡片 3-1:TCC 事务的空回滚与防悬挂

面试问题 TCC 的空回滚和防悬挂是什么?如何实现?
考察点 是否理解 TCC 执行时序异常;幂等设计能力;事务协调器与资源管理器的交互细节。
回答框架
  1. 概念解释:空回滚 = Try 未执行就收到 Cancel;防悬挂 = Cancel 先到后 Try 才到,需拒绝本次 Try。
  2. 实现手段:事务日志记录 Try 执行标识 + 全局事务 ID;Cancel 时检查标识,无则执行空回滚;Try 时检查是否已 Cancel,若已 Cancel 则直接返回成功(防止悬挂)。
示例回答 “空回滚指 Try 未成功但收到 Cancel,需在 Cancel 方法中判断本地事务是否已预留资源,若无则直接返回成功。防悬挂指 Cancel 先于 Try 到达,需在 Try 入口检查是否已存在 Cancel 记录,如果存在则忽略 Try 操作并返回成功,避免资源被无效预留。”
可能追问
  • TCC 的 Confirm 失败如何处理?
  • Seata 框架如何解决空回滚?
  • 幂等号生成策略?
避坑提醒 切忌只背概念,必须结合代码或配置举例。防悬挂常被遗忘,是区分初级与高级的关键点。

问答卡片 3-2:Saga 与 TCC 的选型对比

面试问题 你们的支付业务为什么选 Saga 而不是 TCC?
考察点 方案选型判断力;对业务隔离性需求的认知;对补偿/回滚成本的权衡。
回答框架
  1. 分层对比:TCC 要求资源层提供 Try/Confirm/Cancel 接口,侵入性强;Saga 只需正向操作和补偿操作,业务逻辑更自然。
  2. 隔离性:TCC 通过预留资源实现一定程度隔离;Saga 无隔离,适合最终一致性业务(如支付→履约)。
  3. 场景匹配:长链路、异步化、允许中间状态查询的场景选择 Saga;短链路、高并发、需强隔离选 TCC。
示例回答 “我们选 Saga 是因为支付→出库→物流流水线超过 5 个节点,TCC 的 Try 阶段要冻结库存和余额,冻结时间过长会导致资金利用率下降。Saga 通过状态机 + 补偿表,在出错时回滚已完成步骤,更符合我们的长事务场景。”
可能追问
  • 补偿操作如何保证幂等?
  • Saga 的编排模式与协同模式区别?
  • 如果补偿失败怎么办?
避坑提醒 避免化说法,如“TCC 一定更好”。要结合业务特点(隔离需求、事务时长、团队能力)说明理由。

问答卡片 3-3:本地消息表 vs 事务消息

面试问题 你们在支付对账场景中,为什么用 RocketMQ 事务消息而不是本地消息表?
考察点 方案优缺点对比;消息中间件特性理解;对运维成本和一致性的权衡。
回答框架
  1. 本地消息表:依赖本地事务写消息表 + 定时扫表投递,实现简单但轮询延迟高,消息表与业务库耦合。
  2. 事务消息:RocketMQ 半消息机制 + 回查接口,无轮询、低延迟、消息表解耦。
  3. 选型结论:若团队已落地 RocketMQ 且对延迟敏感(如支付结果通知),选事务消息;若消息量小、对延迟容忍度高,本地消息表更轻量。
示例回答 “我们选择 RocketMQ 事务消息,因为支付成功通知需要在 1 秒内到达下游。本地消息表定时扫表间隔至少 5 秒,不满足延迟要求。事务消息在半消息 commit 后立即投递,配合回查接口保证最终一致。”
可能追问
  • 事务消息的回查接口幂等怎么设计?
  • 消息重复消费你怎么处理?
  • 本地消息表如何避免消息积压?
避坑提醒 不要只说“事务消息更好”,要提及其依赖 MQ 服务稳定性、回查接口的可达性,以及半消息超时清理问题。

项目案例:资金账户聚合转账 —— TCC 分布式事务落地

项目名称 聚合支付网关-账户间转账 TCC 改造
业务背景 用户从 A 银行卡转账至 B 平台余额,需经过:渠道方扣款 → 平台入账 → 通知三方。三个子系统独立部署,必须保证“扣款成功”与“入账成功”严格一致。
规模指标 日均交易 50 万笔;单笔金额上限 5 万;P99 延迟 < 800ms;要求数据最终一致窗口 < 30s。
技术栈 Spring Boot + Seata (TCC 模式) + Redis (分布式锁 + 幂等表) + MySQL (分库分表) + RocketMQ (失败重试/补偿通知)
核心难点
  • 空回滚与防悬挂:渠道方超时后 Cancel 先到,Try 后到引起资源异常。
  • 幂等设计:渠道扣款接口非幂等,必须用本地事务 + 全局事务 ID 去重。
  • Confirm 失败:入账成功后通知下游超时,需异步重试且保证覆盖 Cancel 后的补偿。
面试高频追问
  • 你们如何保证 Try 阶段不会因为锁超时导致全局事务挂死?
  • Seata 的全局锁与本地数据库行锁冲突怎么排查?
  • 为什么不用 Saga 而用 TCC?
  • Cancel 时发现资源已被业务使用(如用户已消费)怎么处理?
可迁移回答话术
“我们在 Try 阶段为每个资源设置全局事务 ID 和状态字段,Cancel 时先检查状态,避免重复回滚。同时设计了一个轻量级调度任务,定期扫描处于‘悬挂’状态的事务,执行人工介入或自动补偿。”
本章面试特别提示:不同大厂侧重点略有差异:阿里系偏好 TCC + Seata,常深挖二阶段异常处理;美团/字节更关注 Saga 与消息对账的工程落地;支付类外企强调分布式事务原理(如 CAP、BASE)与 Paxos/Raft 的关联。准备时,三个方向都要覆盖 1-2 个真实案例。