本文目录导读:

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 等成熟框架快速接入。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播