← 返回目录课程:《答辩面试高频问题与参考回答》
04

04 分布式事务:TCC 与 Saga 模式在订单系统的补偿设计

基于 Seata 实现 TCC 空回滚与 Saga 逆向补偿,避免数据不一致
章节导航 难度:入门类型:工程实战
目标结果

掌握答辩面试高频问题在分布式事务中的定位、故障信号与验证顺序。

关键词
模块接口配置流程状态
交付物
架构图代码片段排障表检查清单
本节对象

围绕 答辩面试高频问题、参考回答、分布式事务 展开。

订单补偿设计中的核心陷阱:TCC 与 Saga 的空回滚问题

现场定位记录

  • 对象/模块:典型技术应用现场里的分布式事务
  • 问题现象:典型技术应用现场里分布式事务一开始改动很快,需求增加后出现状态外泄、循环依赖和接口膨胀。
  • 验证动作:先把职责边界、私有状态、依赖方向和验证入口画清,再拆出最小可替换接口。
  • 工程判断:架构重构不是多建文件夹,而是让变化点被隔离、被验证、被替换。

分布式事务的工程关键是把分布式事务的模式在订单系统的补偿设计、失败路径和空回滚同时定义出来,不能只写能跑通的主流程。

概念块:分布式事务

分布式事务要围绕分布式事务讲清 分布式事务、模式在订单系统的补偿设计、空回滚,让读者能直接对应到真实调试和交付动作。

  • 分布式事务
  • 模式在订单系统的补偿设计
  • 空回滚
  • 逆向补偿

在分布式事务的面试中,“空回滚”是区分候选人是否具备实战经验的关键考点。当使用 Seata 实现 TCC(Try-Confirm-Cancel)或 Saga 模式时,若第二阶段(Cancel/补偿)执行时第一阶段(Try/正向)并未成功完成,系统必须能识别并安全跳过,否则会导致数据凭空扣减或状态错乱。本节聚焦于“答辩面试高频问题(Interview High-Frequency Questions)与参考回答(Reference Answers)”中关于空回滚的工程实现,重点说明 Seata 框架下的处理链路与容易出错的步骤

核心判断:空回滚不是 Bug,而是分布式事务框架必须内置的容错机制。面试官常通过“TCC 空回滚(TCC Empty Rollback)”和“Saga 空回滚(Saga Empty Rollback)”来考察候选人对事务边界与幂等设计的理解。下面以订单系统的库存扣减为例,拆解 Seata 的实现路径

关键判断

空回滚的根源在于网络超时或服务宕机导致 Try 请求未到达,但事务协调器已感知超时并直接触发 Cancel。此时若 Cancel 不做空回滚检查,会错误地执行“释放资源”逻辑,造成数据不一致

Seata TCC 模式下的空回滚与悬挂控制

分布式事务:TCC 与 Saga 模式在订单系统的补偿设计 · 数据流图
flowchart LR subgraph L0["采集 / 监控"] N0["TCC"] end subgraph L1["判断 / 计算"] N1["Seata"] end subgraph L2["调整 / 执行"] N2["TCC空回滚"] end subgraph L3["验证 / 回写"] N3["Seata空回滚"] end N0 -->|采样| N1 N1 -->|判断| N2 N2 -->|调整| N3 N3 -->|反馈| N0 classDef cgNodeStyle fill:#ede9fe,stroke:#7c3aed,color:#3b0764,stroke-width:2px; class N0,N1,N2,N3 cgNodeStyle; linkStyle default stroke:#64748b,stroke-width:1.6px;

图表解读:对应正文“Seata TCC 模式下的空回滚与悬挂控制”。这张图以最终 Mermaid 图中的真实节点为准,围绕“分布式事务:TCC 与 Saga 模式在订单系统的补偿设计”展开。读图顺序:采集 / 监控 → TCC → 判断 / 计算 → Seata → 调整 / 执行 → TCC空回滚

节点说明:【TCC节点】表示TCC 是“分布式事务:TCC 与 Saga 模式在订单系统的补偿设计”图中的关键对象,下一步通过“指向”连接到 Seata,标出该节点在主线中的位置;检查重点:相关操作、判断条件或常见问题。

分布式事务:TCC 与 Saga 模式在订单系统的补偿设计
硬件接线分布式事务:TCC 与 Saga 模式在订单…接线关系硬件接口检查点答辩面试高频问题与参考回答答辩面试高频问题与参考回…分布式事务分布式事务VCCGNDSCL/CLKSDA/DATASaga主控端模块端

Seata 的 TCC 模式通过 @TwoPhaseBusinessAction 注解定义 Try、Confirm、Cancel 三个阶段。空回滚的防御逻辑通常写在 Cancel 方法中,通过检查本地事务日志(如一张 tcc_log 表)来判断 Try 阶段是否真正执行过

实现路径:基于事务日志的状态判断

在订单系统的库存服务中,Try 阶段会插入一条状态为 0(已预留)的日志记录;Cancel 阶段执行时,若发现该记录不存在,则直接返回成功(空回滚)。Seata 客户端还提供了 BusinessActionContext 上下文,可从中获取 actionName 和 branchId 来辅助判断

// TCC 接口定义
@TwoPhaseBusinessAction(name = "inventoryTcc", commitMethod = "confirm", rollbackMethod = "cancel")
public boolean tryDecrease(BusinessActionContext context,
                           @BusinessActionContextParameter(paramName = "orderId") String orderId,
                           @BusinessActionContextParameter(paramName = "productId") Long productId,
                           @BusinessActionContextParameter(paramName = "count") Integer count);
// Cancel 方法中的空回滚处理
public boolean cancel(BusinessActionContext context) {
    String orderId = context.getActionContext("orderId").toString();
    // 检查本地事务日志:若 Try 阶段未写入记录,则直接返回 true
    TccLog log = tccLogMapper.selectByOrderId(orderId);
    if (log == null) {
        // 空回滚:记录一条空回滚日志,避免重复处理
        tccLogMapper.insert(new TccLog(orderId, "EMPTY_ROLLBACK"));
        return true;
    }
    // 正常回滚:恢复库存并更新日志状态
    inventoryMapper.revertStock(log.getProductId(), log.getCount());
    tccLogMapper.updateStatus(orderId, "CANCELED");
    return true;
}

阅读这段代码时,重点看分布式事务的伪代码,以及它如何用稳定性与可观测性证明结果可复测。

上述代码的关键在于:tccLogMapper.selectByOrderId(orderId) 返回 null 时,Cancel 方法必须返回 true 而非抛出异常。如果返回 false 或抛出异常,Seata 会不断重试 Cancel,导致空回滚日志重复写入,形成“悬挂事务”

工程动作

在 Seata 的 TCC 模式中,务必为每个分支事务维护一张幂等控制表(如 tcc_log),并在 Cancel 方法中优先执行“空回滚判断”。面试时可补充说明:空回滚日志本身也需要幂等,通常使用唯一索引(orderId + actionName)来防止重复插入

Saga 模式下的逆向补偿与空回滚防御

Saga 模式通过编排正向服务与逆向补偿服务来实现长事务。在 Seata 的 Saga 实现中,状态机引擎会记录每个节点的执行状态。当正向服务因超时未响应时,引擎会直接调用该节点的补偿服务——此时若正向服务实际并未执行,补偿服务就面临空回滚问题

基于状态机上下文的空回滚检查

Seata Saga 的状态机上下文(StateMachineContext)中包含了每个节点的 status 字段。补偿服务可以通过读取该字段来判断正向节点是否真正完成。以下是一个基于 JSON 配置的 Saga 状态机片段:

{
  "name": "orderSaga",
  "states": [
    {
      "name": "deductInventory",
      "type": "ServiceTask",
      "serviceName": "inventoryService",
      "serviceMethod": "deduct",
      "compensateState": "compensateDeduct"
    },
    {
      "name": "compensateDeduct",
      "type": "ServiceTask",
      "serviceName": "inventoryService",
      "serviceMethod": "compensateDeduct",
      "input": [
        {"from": "context", "expression": "#root.deductInventory.status"}
      ]
    }
  ]
}

阅读这段代码时,重点看分布式事务的伪代码,以及它如何用稳定性与可观测性证明结果可复测。

在补偿方法 compensateDeduct 中,通过 deductInventory.status 判断:若状态为 SU(成功),则执行真正的库存回滚;若状态为 FA(失败)或 UN(未知),则直接返回成功,实现空回滚。Seata 的 Saga 引擎在调用补偿服务前,会自动将上下文中的状态注入到服务方法的参数中

对比表
对比表:TCC 与 Saga 在空回滚处理上的差异
维度方案A方案B判断建议
空回滚判断依据本地事务日志表(如 tcc_log)状态机上下文中的节点状态TCC 适合短事务、高一致性;Saga 适合长流程、可…
幂等控制方式唯一索引 + 状态字段状态机引擎内置幂等,补偿服务需自行检查TCC 需开发者自行实现;Saga 框架提供部分支持
悬挂事务风险Cancel 先于 Try 到达时产生,需空回滚日志防重补偿先于正向到达时产生,需检查节点状态两者都需要在补偿/取消入口做空回滚判断

真实项目场景

某电商订单系统使用 Seata TCC 管理库存扣减。一次网络抖动导致 Try 请求超时,Seata 协调器在 30 秒后触发了 Cancel。由于 Cancel 方法未做空回滚检查,直接执行了库存回滚(将冻结库存加回可用库存),而 Try 请求在超时后实际到达并扣减了库存。最终导致可用库存被多加了 100 件,出现超卖。修复方案:在 Cancel 方法中增加 tcc_log 检查,若 Try 记录不存在,则记录空回滚日志并直接返回成功

问题对照与修正

  1. 常见偏差:把分布式事务直接写进主流程,只验证能跑通,留下分布式事务边界、状态和验证口径不清。
  2. 修正做法:把分布式事务收敛成稳定接口,明确伪代码和失败返回。
  3. 复核方式:构造一个异常输入或超时场景,用日志、断言和稳定性与可观测性证明修复有效。

异常信号与处理边界:如何验证空回滚逻辑的正确性

面试中,面试官常会追问:“你怎么验证你的空回滚逻辑是安全的?” 以下是一份判断标准表,用于自检或评审代码

判断标准表摘要
判断维度好的表现差的表现例子
常见误区

许多开发者只在 Cancel 方法中做空回滚检查,却忽略了 Try 方法中的“悬挂控制”。当 Cancel 先于 Try 到达并记录了空回滚日志后,Try 请求才到达——此时 Try 应当直接返回成功(或不做任何操作),否则会形成悬挂事务。Seata 官方文档将此称为“悬挂控制(Hanging Control)”,是 TCC 实现的三大难点之一

在“答辩面试高频问题与参考回答(Interview High-Frequency Questions and Reference Answers)”中,关于“review_summary 空回滚(Review Summary Empty Rollback)”和“考回答空回滚(Exam Answer Empty Rollback)”的讨论,本质上都是考察候选人是否理解:空回滚不是异常,而是分布式事务框架必须支持的正常流程。面试时,建议从“幂等性”“悬挂控制”“事务日志设计”三个层面展开回答,并结合 Seata 的具体 API 说明实现细节

细节补充:

分布式事务里的参数、字段和配置细节不要抢走主线;先确认对象边界、失败路径和验证信号,再回到细节逐项核对。

分布式事务最容易失控的是分布式事务边界、状态和验证口径不清;落地前必须核对伪代码,不要只凭主流程跑通就收尾。

代码理解题

  1. Q1

    阅读分布式事务代码时,先看哪里?

    参考思路:

    先看分布式事务的入口参数、返回结果和主要分支,不要直接从细节语句开始。

  2. Q2

    哪个分支最需要确认?

    参考思路:

    优先确认失败、空值、超时或边界值分支是否有明确处理动作。

  3. Q3

    如何证明自己理解了代码?

    参考思路:

    写一个围绕配置验证的最小调用样例,并说明每一步的输入与结果。

  4. Q4

    怎样验收代码行为?

    参考思路:

    用一组正常样例和一组异常样例验证分布式事务行为没有跑偏。