Seata TCC 与 SAGA 模式源码阅读
AT 模式自动处理 SQL,但有些场景它覆盖不了:跨服务调用、非数据库资源、需要业务自定义补偿。这时就需要 TCC 与 SAGA。本文从源码拆解 TCC 的 Try-Confirm-Cancel 回调机制和 SAGA 状态机引擎。
TCC 模式源码
TCC 的整体思想
TCC 三阶段:
Try :预留业务资源(如冻结金额)
Confirm:确认执行业务(冻结转扣减)
Cancel :取消预留(释放冻结)
由 TC 统一协调:全部 Try 成功 → Confirm;任一 Try 失败 → Cancel核心注解
java
// 定义 TCC 资源接口
@LocalTCC
public interface AccountAction {
// Try 阶段
@TwoPhaseBusinessAction(
name = "accountAction",
commitMethod = "confirm",
rollbackMethod = "cancel"
)
boolean tryFreeze(@BusinessActionContextParameter(paramName = "accountId") Long accountId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
// Confirm 阶段
boolean confirm(BusinessActionContext context);
// Cancel 阶段
boolean cancel(BusinessActionContext context);
}注解解析:
@LocalTCC :标记这是一个 TCC 资源(本地模式)
@TwoPhaseBusinessAction:
├─ name :资源名称,全局唯一
├─ commitMethod:Confirm 方法名
└─ rollbackMethod:Cancel 方法名
@BusinessActionContextParameter:
├─ 标记的参数会存入 BusinessActionContext
└─ 在 Confirm / Cancel 阶段通过 context 取回BusinessActionContext
Try 阶段的入参会被封装进 BusinessActionContext,在二阶段传给 Confirm / Cancel:
BusinessActionContext 内容:
├─ xid :全局事务 ID
├─ branchId :分支事务 ID
├─ actionName :TCC 资源名
├─ actionContext:Try 阶段 @BusinessActionContextParameter 标记的参数
└─ 扩展属性执行流程源码
1. 调用 tryFreeze(Try 阶段)
├─ TCCResourceManager 拦截
├─ 向 TC 注册分支事务(BranchType.TCC)
└─ 记录 Try 方法调用上下文
2. 全部 Try 成功 → TC 通知 Confirm
├─ 根据 actionName 找到 Confirm 方法
└─ 反射调用 confirm(BusinessActionContext)
3. 任一 Try 失败 → TC 通知 Cancel
├─ 找到 Cancel 方法
└─ 反射调用 cancel(BusinessActionContext)核心类
java
// RM 侧的管理器,负责注册分支与执行二阶段回调
public class TCCResourceManager implements ResourceManager {
// 注册分支事务
public Long branchRegister(BranchType branchType, String resourceId,
String clientId, String xid, ...) {
// 向 TC 发起注册,返回 branchId
}
// 二阶段提交:执行 Confirm
public BranchStatus branchCommit(String xid, long branchId,
String resourceId, String applicationData) {
// 1. 根据 resourceId 找到 TCC 资源
// 2. 取出 Confirm 方法,反射调用
// 3. 返回执行结果
}
// 二阶段回滚:执行 Cancel
public BranchStatus branchRollback(String xid, long branchId,
String resourceId, String applicationData) {
// 同理调用 Cancel 方法
}
}分支事务状态流转
TCC 分支状态:
Registered(已注册,Try 完成)
├─ 全局提交 → Committed(Confirm 完成)
└─ 全局回滚 → Rollbacked(Cancel 完成)
异常情况:
├─ Confirm 执行失败 → 标记为提交失败,重试 Confirm
└─ Cancel 执行失败 → 标记为回滚失败,重试 Cancel防悬挂与空回滚
TCC 最容易踩的坑是悬挂(Cancel 先于 Try)与空回滚(Try 没成功就 Cancel)。
Seata 的解决思路:在 Try 阶段记录一张"执行记录表"
Try 执行时:
1. 先查询执行记录:若已存在 → 说明之前 Cancel 过 → 直接返回(拒绝悬挂)
2. 不存在 → 插入记录(状态:Trying)
3. 执行预留资源操作
Cancel 执行时:
1. 查询执行记录:若不存在 → 空回滚 → 只插入"已回滚"记录,不执行业务
2. 存在且 Trying → 更新状态 → 执行释放资源
3. 存在且已回滚 → 幂等返回(防止重复 Cancel)
Confirm 执行时:
1. 存在且 Trying → 更新状态 → 执行确认
2. 已确认 → 幂等返回SAGA 模式源码
SAGA 的整体思想
SAGA 把长事务拆成步骤序列,每步一个本地事务 + 一个补偿:
正向:T1 → T2 → T3
失败:T3 失败 → 依次补偿 C2 → C1
Seata SAGA 是"编排式":用状态机定义流程,TC 侧状态机引擎驱动执行状态机模型
状态机三个核心概念:
├─ State:状态(每个步骤一个状态)
├─ Transition:转移(状态间跳转,带条件与补偿)
└─ Compensate:补偿(失败后按反向顺序触发)状态机定义(JSON)
json
{
"name": "createOrderSaga",
"startState": "deductStock",
"states": {
"deductStock": {
"type": "ServiceTask",
"serviceName": "stock-service",
"serviceMethod": "deduct",
"compensateState": "compensateStock",
"next": "deductBalance"
},
"deductBalance": {
"type": "ServiceTask",
"serviceName": "account-service",
"serviceMethod": "deduct",
"compensateState": "compensateBalance",
"next": "success"
},
"compensateStock": {
"type": "ServiceTask",
"serviceName": "stock-service",
"serviceMethod": "compensate"
},
"compensateBalance": {
"type": "ServiceTask",
"serviceName": "account-service",
"serviceMethod": "compensate"
},
"success": {
"type": "Succeed"
}
}
}状态机引擎执行流程
执行流程:
1. 加载状态机定义 → 构建 StateMachine 模型
2. 找到 startState,执行对应 ServiceTask
3. 执行成功 → 按 next 转移到下一个状态
4. 执行失败 → 沿补偿链反向执行 compensateState
5. 全部补偿完成 → 状态机标记为失败
状态机实例:
每次执行业务生成一个 StateMachineInstance
记录当前状态、执行历史、补偿状态,持久化到数据库核心类
java
// 状态机引擎入口
public interface StateMachineEngine {
// 启动一个状态机实例
StateMachineInstance start(String stateMachineName,
String businessKey,
Map<String, Object> businessParams);
// 重新执行(故障恢复后继续)
StateMachineInstance reloadStateMachine(String instId);
}
// 状态机实例:一次执行的快照
public class StateMachineInstance {
private String id; // 实例 ID
private String stateMachineName; // 状态机名
private String businessKey; // 业务键
private String status; // 执行状态
private String currentState; // 当前状态
private List<StateInstance> stateList; // 已执行的状态历史
}状态执行器
Seata 内置多种 State 类型:
├─ ServiceTaskState:调用业务服务方法
├─ CompensationState:补偿执行
├─ ChoiceState:分支选择(条件判断)
├─ ForkState / JoinState:并行分支与汇合
├─ SucceedState / FailState:成功 / 失败终点
└─ SubStateMachineState:嵌套子状态机
通过 StateMachineParser 解析 JSON 定义,
每种 State 对应一个执行器(StateHolder 管理)。补偿事务设计
SAGA 补偿设计要点:
├─ 幂等:同一补偿可能被触发多次(失败重试)
├─ 语义对等:T2 的补偿 C2 必须能抵消 T2 的副作用
│ 例子:T2 扣 100 → C2 加回 100(不能用"置零")
├─ 顺序:补偿严格按正向的逆序执行
└─ 超时:补偿调用也要有超时与重试,防止补偿自身失败状态机持久化
Seata 提供状态机日志表(可选):
├─ seata_state_machine_def:状态机定义
├─ seata_state_machine_inst:状态机实例
└─ seata_state_inst:各状态执行记录
作用:
├─ 故障恢复:重启后 reloadStateMachine 继续执行
├─ 监控:查看每个步骤的执行情况
└─ 对账:人工排查未完成实例TCC 与 SAGA 选型对比
| 对比项 | TCC | SAGA |
|---|---|---|
| 一致性 | 较强(Try 预留资源) | 最终一致(中间状态可见) |
| 业务侵入 | 高(三接口) | 中(状态机 JSON) |
| 资源锁 | 不锁库(预留式) | 不锁库(直接执行) |
| 适用场景 | 资金、库存强管控 | 多步骤长流程 |
| 故障恢复 | Confirm/Cancel 重试 | 状态机断点续跑 |
| 复杂度 | 中 | 高(需设计状态机) |
选型建议:
├─ 单资源强管控 → TCC
├─ 多服务长流程 → SAGA
├─ 两者都有 → 混合:TCC 管核心资金,SAGA 管业务流程
└─ 只是想快速跨库 → AT 模式(本文之外的最佳性价比)总结
TCC 与 SAGA 把"补偿"从框架自动(AT 的 undo_log)变成业务显式实现:TCC 通过 @TwoPhaseBusinessAction 定义 Try/Confirm/Cancel 三个回调,由 TC 统一驱动;SAGA 用状态机 JSON 描述步骤与补偿,由状态机引擎驱动执行并支持断点续跑。两者的源码核心都是"注册分支 → TC 协调 → 反射回调",理解这一点,再看空回滚、悬挂、补偿幂等这些生产问题,就有了解法。