TCC案例

wen java案例 1

本文目录导读:

TCC案例

  1. 案例背景:电商下单
  2. TCC 核心概念映射
  3. 完整业务时序图(正常流程)
  4. 核心代码逻辑(Java 伪代码)
  5. 异常情况与幂等性处理(重点)
  6. TCC 的优缺点总结(案例复盘)
  7. 实际应用框架
  8. 案例分析总结:关键点

这是一个关于TCC(Try-Confirm-Cancel) 分布式事务模式的详细案例解析,TCC 是解决微服务架构下数据一致性问题的常用方案,尤其适用于强一致性要求较高的业务场景。

为了让你能直观理解,我将通过一个电商下单扣库存的经典案例,从业务背景代码逻辑再到异常处理进行全流程拆解。


案例背景:电商下单

假设我们有三个微服务:

  1. 订单服务:创建订单。
  2. 库存服务:扣减商品库存。
  3. 账户服务:扣减用户余额。

业务需求:用户下单时,需要同时创建订单扣减库存扣减余额,这三个操作必须要么全部成功,要么全部失败(不能出现订单创建了但库存没扣,或者钱扣了但订单没创建的情况)。


TCC 核心概念映射

TCC 将每个业务操作拆分为两个阶段(Try 阶段 + Confirm/Cancel 阶段),针对上述业务,我们定义如下:

服务 Try(预留资源) Confirm(确认执行业务) Cancel(取消/回滚预留资源)
库存服务 冻结库存(将商品库存从“可用”转为“冻结”) 扣减库存(将“冻结”的库存删除) 解冻库存(将“冻结”的库存回退为“可用”)
账户服务 冻结金额(将用户余额从“可用”转为“冻结”) 扣减金额(将“冻结”的金额扣除) 解冻金额(将“冻结”的金额回退为“可用”)
订单服务 创建订单(状态:待确认) 更新订单(状态:已确认/成功) 更新订单(状态:已取消/关闭)

完整业务时序图(正常流程)

用户请求下单
    |
    |   (1) 全局事务管理器(TM)发起事务
    v
+-------------------+
|      Try 阶段      |  <-- 尝试预留资源(此时并未真正扣减,只是“占坑”)
+-------------------+
    |  (2) 调用库存服务 -> 冻结库存 100 件(成功)
    |  (3) 调用账户服务 -> 冻结余额 1000 元(成功)
    |  (4) 调用订单服务 -> 创建订单(状态:待确认,成功)
    v
(若全部 Try 成功)
    |
    |  (5) TM 发起 Confirm 提交
    v
+-------------------+
|    Confirm 阶段    |  <-- 真正执行业务变更
+-------------------+
    |  (6) 库存服务 -> 扣减冻结库存(成功)
    |  (7) 账户服务 -> 扣减冻结余额(成功)
    |  (8) 订单服务 -> 更新订单状态为“支付成功”(成功)
    v
        全局事务完成,所有数据最终一致。

核心代码逻辑(Java 伪代码)

这是最关键的部分,也就是如何保证数据一致性

库存服务

// 库存表
// 字段:total_stock(总库存), frozen_stock(冻结库存)
/** Try 阶段:锁定库存 */
@Transactional // 本地事务,保证该步骤内的操作原子性
public boolean tryDeductStock(Long productId, int quantity) {
    // 1. 检查可用库存是否足够:total_stock - frozen_stock >= quantity
    // 2. SQL: UPDATE stock SET frozen_stock = frozen_stock + ? 
    //        WHERE product_id = ? AND (total_stock - frozen_stock) >= ?
    // 3. 如果影响行数为 0,返回 false(表示库存不足,触发 Cancel)
    return true; // 或者抛出异常
}
/** Confirm 阶段:真正扣减 */
@Transactional
public boolean confirmDeductStock(Long productId, int quantity) {
    // 1. 将“冻结库存”转为“扣减”
    // SQL: UPDATE stock SET total_stock = total_stock - ?, 
    //      frozen_stock = frozen_stock - ? WHERE product_id = ?
    return true;
}
/** Cancel 阶段:解冻库存 */
@Transactional
public boolean cancelDeductStock(Long productId, int quantity) {
    // 1. 释放“冻结库存”
    // SQL: UPDATE stock SET frozen_stock = frozen_stock - ? 
    //      WHERE product_id = ?
    return true;
}

账户服务(逻辑类似,略)


异常情况与幂等性处理(重点)

分布式事务最大的挑战在于网络超时机器宕机

场景 1:Try 阶段部分失败(例如库存不足)

库存服务 Try 成功(冻结库存)。
2. 账户服务 Try 失败(余额不足,抛出异常)。
3. 全局事务管理器(TM)收到失败信号。
    -> TM 调用 **Cancel** 接口,回滚之前成功的 Try 操作。
即调用库存服务的 Cancel——将冻结的库存解冻。
**结果**:虽然库存 Try 成功,但最终被 Cancel,数据库没有留下脏数据。

场景 2:Confirm 阶段超时(网络抖动)

所有 Try 成功。
2. TM 调用库存服务 Confirm(成功),但网络超时,TM 未收到响应。
3. TM 调用库存服务 Confirm(重试)。
**核心问题**:如果第一次 Confirm 成功了,第二次重复调用 Confirm 会导致库存被重复扣减!
**解决方案(幂等性)**:
必须确保 Confirm 和 Cancel 接口是**幂等**的。
- 在数据库表中添加 **事务日志表** 或 **状态字段**(如 `tx_id`)。
- 执行 Confirm 前,先查询该 `tx_id` 是否已处理过。
- 如果已处理,直接返回成功,不再执行扣减逻辑。

场景 3:Cancel 阶段失败(极端情况)

Cancel 也失败了(例如数据库宕机),TM 会不断重试,直到成功。
如果一直失败,则需要进入“人工介入”或“定时对账”流程。

TCC 的优缺点总结(案例复盘)

优点

  1. 强一致性:相比 MQ 的最终一致性,TCC 在 Confirm 阶段完成后数据是强一致的。
  2. 业务隔离:Try 阶段通过“冻结”资源,不会占用其他正常用户的可用资源,并发效率高(例如库存不足时,其他人依然可以购买其他商品)。
  3. 灵活性高:可以根据业务逻辑定制 Confirm 和 Cancel,不局限于数据库操作。

缺点

  1. 开发成本高:每个业务都需要实现 TryConfirmCancel 三个方法,代码量翻倍。
  2. 侵入性强:业务逻辑需要改造,将原本的单库事务拆分为两阶段。
  3. 对“空回滚”和“悬挂”要求高:需要处理中间状态(Try 还没执行完,Cancel 就来了)。
  4. 不适合简单的读多写少场景:太重了。

实际应用框架

在实际开发中,你不会手写这些逻辑,通常会使用成熟的分布式事务框架:

  • Seata(阿里开源,推荐): @GlobalTransactional 注解 + 自动生成 TCC 模式。
  • Hmily:网易开源的 TCC 框架。
  • ByteTCC

案例分析总结:关键点

这个案例的核心思想是:“先占坑,后填坑”

  • Try:不真正扣钱/扣货,而是把你的账号余额“冻结”起来,把仓库的商品“锁住”。
  • Confirm:既然大家都有货也有钱,那就正式“扣款扣货”。
  • Cancel:只要有一方不行(没钱或没货),立刻把之前“冻结”的全部释放。

注意:TCC 并不能解决所有问题,它在设计时牺牲了一定的吞吐量极高的实现复杂度,用于换取数据的强一致性,在业务中对资金、库存敏感度极高的场景(如电商支付、秒杀)中应用广泛。

抱歉,评论功能暂时关闭!