本文目录导读:

TCC(Try-Confirm/Cancel)是一种分布式事务解决方案,它通过将事务拆分为三个步骤来保证最终一致性,适用于对一致性要求较高的场景(如金融、交易系统),下面详细介绍其实现原理、步骤、以及代码示例。
TCC 的概念
TCC 分为三个阶段:
- Try:尝试执行业务,预留资源(比如锁定库存、冻结资金)。
- Confirm:确认执行业务,真正完成操作(比如扣减库存、扣款)。
- Cancel:取消执行业务(回滚 Try 阶段预留的资源)。
TCC 的核心理念是将“业务提交”与“业务补偿”显式地定义出来,由事务协调器来驱动这些操作。
TCC 的工作流程
假设我们有一个简单的转账业务:从账户 A 扣减 100 元,向账户 B 增加 100 元。
-
Try 阶段:
- 检查账户 A 余额 ≥ 100,冻结 100 元(在数据库中增加一个冻结金额字段)。
- 检查账户 B 有效性(不需要冻结,因为增加金额没有风险)。
- 如果两个 Try 都成功,则进入 Confirm 阶段;如果任何一个失败,则进入 Cancel 阶段。
-
Confirm 阶段:
- 账户 A:正式扣减 100 元(减少余额,同时清除冻结金额)。
- 账户 B:增加 100 元。
- Confirm 阶段不应失败(通常是幂等的,失败则重试)。
-
Cancel 阶段:
- Try 阶段失败(A 余额不足),需要释放 A 的冻结金额。
- Confirm 阶段失败(罕见),则通过重试或人工处理保证最终一致。
TCC 的实现要求
- 幂等性:Confirm 和 Cancel 操作必须支持重复调用,且结果一致(通常通过唯一事务 ID 或状态机保证)。
- 资源预留:Try 阶段不能直接提交业务,而是预留资源(如“冻结”、“临时记录”)。
- 事务协调器:需要一个中心化或分布式的协调器来记录事务状态,驱动 Try → Confirm/Cancel 流程。
- 补偿机制:Cancel 操作必须能干净地回滚 Try 的预留。
TCC 的代码实现示例(Java + 数据库)
下面用一个简单的伪代码展示 TCC 的实现,其中使用数据库状态字段和唯一事务 ID 来保证幂等性和最终一致性。
数据库表设计
-- 账户表,增加冻结金额字段
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(18,2),
frozen DECIMAL(18,2) DEFAULT 0
);
-- 事务记录表,用于幂等控制
CREATE TABLE tcc_transaction (
transaction_id VARCHAR(64) PRIMARY KEY,
status VARCHAR(20) -- 'TRYING', 'CONFIRMED', 'CANCELED'
);
TCC 服务实现(账户服务)
@Service
public class AccountService {
@Autowired
private JdbcTemplate jdbc;
// Try:尝试冻结金额
@Transactional
public boolean tryDebit(String transactionId, int accountId, BigDecimal amount) {
// 幂等检查:如果该事务已执行过 Try,则返回已有结果
String status = jdbc.queryForObject(
"SELECT status FROM tcc_transaction WHERE transaction_id = ?",
String.class, transactionId);
if (status != null) {
return "TRYING".equals(status) || "CONFIRMED".equals(status);
}
// 1. 检查余额是否充足
BigDecimal balance = jdbc.queryForObject(
"SELECT balance FROM account WHERE id = ?", BigDecimal.class, accountId);
if (balance.compareTo(amount) < 0) {
return false;
}
// 2. 插入事务记录(TRYING状态)
jdbc.update("INSERT INTO tcc_transaction (transaction_id, status) VALUES (?, 'TRYING')",
transactionId);
// 3. 冻结金额(增加 frozen 字段,不减少 balance)
jdbc.update("UPDATE account SET frozen = frozen + ? WHERE id = ?", amount, accountId);
return true;
}
// Confirm:确认扣款
@Transactional
public void confirmDebit(String transactionId, int accountId, BigDecimal amount) {
// 幂等控制:如果已经 Confirm,直接返回
String status = jdbc.queryForObject(
"SELECT status FROM tcc_transaction WHERE transaction_id = ?",
String.class, transactionId);
if ("CONFIRMED".equals(status)) {
return;
}
// 1. 减少余额,同时减少冻结金额
jdbc.update("UPDATE account SET frozen = frozen - ?, balance = balance - ? WHERE id = ?",
amount, amount, accountId);
// 2. 更新事务状态为 CONFIRMED
jdbc.update("UPDATE tcc_transaction SET status = 'CONFIRMED' WHERE transaction_id = ?",
transactionId);
}
// Cancel:取消扣款(释放冻结)
@Transactional
public void cancelDebit(String transactionId, int accountId, BigDecimal amount) {
// 幂等控制
String status = jdbc.queryForObject(
"SELECT status FROM tcc_transaction WHERE transaction_id = ?",
String.class, transactionId);
if ("CANCELED".equals(status) || "CONFIRMED".equals(status)) {
return;
}
// 1. 释放冻结金额(只是减少 frozen,不改变 balance)
jdbc.update("UPDATE account SET frozen = frozen - ? WHERE id = ?", amount, accountId);
// 2. 更新事务状态为 CANCELED
jdbc.update("UPDATE tcc_transaction SET status = 'CANCELED' WHERE transaction_id = ?",
transactionId);
}
}
事务协调器(简化版)
public class TccCoordinator {
public void executeTransfer(String txId, int fromAccount, int toAccount, BigDecimal amount) {
AccountService fromService = ...; // 注入
// 1. Try 阶段
boolean tryResult = fromService.tryDebit(txId, fromAccount, amount);
boolean tryCreditResult = toService.tryCredit(txId, toAccount, amount);
// tryCredit 类似,但可能不需要冻结(只做检查)
if (tryResult && tryCreditResult) {
// 2. Try 都成功 -> Confirm
try {
fromService.confirmDebit(txId, fromAccount, amount);
toService.confirmCredit(txId, toAccount, amount);
} catch (Exception e) {
// 发生异常则重试 Confirm 直到成功(或人工介入)
log.error("Confirm failed, need retry or manual handle", e);
}
} else {
// 3. 任何一个 Try 失败 -> Cancel
fromService.cancelDebit(txId, fromAccount, amount);
toService.cancelCredit(txId, toAccount, amount);
}
}
}
TCC 的常见实现框架
实际生产环境中,不建议手写 TCC,可以使用成熟的框架:
| 框架 | 特点 |
|---|---|
| Seata(阿里) | 支持 AT、TCC、Saga 模式,社区活跃,接入简单。 |
| ByteTCC | 轻量级 TCC 框架,基于 Spring。 |
| Hmily | 高性能 TCC,支持 Dubbo、Spring Cloud。 |
| TCC-Transaction | 老牌框架,支持分布式协调。 |
这些框架通常提供了:
- 自动的 Try/Confirm/Cancel 调用
- 幂等性保证
- 事务状态持久化
- 失败重试与恢复
TCC 的优缺点与适用场景
优点
- 最终一致性:相比 2PC(两阶段提交),TCC 不需要资源锁住,性能更高。
- 业务灵活性:业务可以在 Try 阶段做资源预留,Cancel 阶段做补偿。
- 可定制化:每一个子事务都可以独立设计补偿逻辑。
缺点
- 侵入性强:业务代码需要显式实现 Try/Confirm/Cancel,改造工作量大。
- 依赖协调器:需要可靠的协调器来保证状态推进。
- 空回滚与悬挂问题:可能发生 Cancel 在 Try 之前执行(如网络超时),需要额外处理(通过事务 ID 幂等+状态机)。
适用场景
- 资金转账、扣库存等强一致性业务。
- 跨数据库、跨服务的分布式事务。
- 对性能要求较高,但不能接受长时间锁资源(如 XA 两阶段锁定资源)。
关键细节:空回滚与悬挂
- 空回滚:Try 未执行,但 Cancel 被调用,解决方法:在 Cancel 中根据事务 ID 判断 Try 是否执行过。
- 悬挂:Try 超时后 Cancel 执行,但 Try 后续到达,解决方法:依赖事务状态机,Try 执行前先检查状态。
通常通过“事务记录表”和“状态机”来避免这些问题(如上面示例中的 tcc_transaction 表)。
TCC 事务通过将业务拆分为 Try(预留)、Confirm(提交)、Cancel(补偿)三个阶段,在保证最终一致性的同时,避免了长时间资源锁定的问题,实现时需要幂等性、状态机和事务协调器的支持,如果不想从零实现,推荐使用 Seata、Hmily、ByteTCC 等成熟框架快速接入。