本文目录导读:

- 案例背景:电商下单
- TCC 核心概念映射
- 完整业务时序图(正常流程)
- 核心代码逻辑(Java 伪代码)
- 异常情况与幂等性处理(重点)
- TCC 的优缺点总结(案例复盘)
- 实际应用框架
- 案例分析总结:关键点
这是一个关于TCC(Try-Confirm-Cancel) 分布式事务模式的详细案例解析,TCC 是解决微服务架构下数据一致性问题的常用方案,尤其适用于强一致性要求较高的业务场景。
为了让你能直观理解,我将通过一个电商下单扣库存的经典案例,从业务背景到代码逻辑再到异常处理进行全流程拆解。
案例背景:电商下单
假设我们有三个微服务:
- 订单服务:创建订单。
- 库存服务:扣减商品库存。
- 账户服务:扣减用户余额。
业务需求:用户下单时,需要同时创建订单、扣减库存、扣减余额,这三个操作必须要么全部成功,要么全部失败(不能出现订单创建了但库存没扣,或者钱扣了但订单没创建的情况)。
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 的优缺点总结(案例复盘)
优点
- 强一致性:相比 MQ 的最终一致性,TCC 在 Confirm 阶段完成后数据是强一致的。
- 业务隔离:Try 阶段通过“冻结”资源,不会占用其他正常用户的可用资源,并发效率高(例如库存不足时,其他人依然可以购买其他商品)。
- 灵活性高:可以根据业务逻辑定制 Confirm 和 Cancel,不局限于数据库操作。
缺点
- 开发成本高:每个业务都需要实现
Try、Confirm、Cancel三个方法,代码量翻倍。 - 侵入性强:业务逻辑需要改造,将原本的单库事务拆分为两阶段。
- 对“空回滚”和“悬挂”要求高:需要处理中间状态(Try 还没执行完,Cancel 就来了)。
- 不适合简单的读多写少场景:太重了。
实际应用框架
在实际开发中,你不会手写这些逻辑,通常会使用成熟的分布式事务框架:
- Seata(阿里开源,推荐):
@GlobalTransactional注解 + 自动生成TCC模式。 - Hmily:网易开源的 TCC 框架。
- ByteTCC。
案例分析总结:关键点
这个案例的核心思想是:“先占坑,后填坑”。
- Try:不真正扣钱/扣货,而是把你的账号余额“冻结”起来,把仓库的商品“锁住”。
- Confirm:既然大家都有货也有钱,那就正式“扣款扣货”。
- Cancel:只要有一方不行(没钱或没货),立刻把之前“冻结”的全部释放。
注意:TCC 并不能解决所有问题,它在设计时牺牲了一定的吞吐量和极高的实现复杂度,用于换取数据的强一致性,在业务中对资金、库存敏感度极高的场景(如电商支付、秒杀)中应用广泛。