TCC事务怎么实现?

wen python案例 2

本文目录导读:

TCC事务怎么实现?

  1. TCC 的概念
  2. TCC 的工作流程
  3. TCC 的实现要求
  4. TCC 的代码实现示例(Java + 数据库)
  5. TCC 的常见实现框架
  6. TCC 的优缺点与适用场景
  7. 关键细节:空回滚与悬挂

TCC(Try-Confirm/Cancel)是一种分布式事务解决方案,它通过将事务拆分为三个步骤来保证最终一致性,适用于对一致性要求较高的场景(如金融、交易系统),下面详细介绍其实现原理、步骤、以及代码示例。


TCC 的概念

TCC 分为三个阶段:

  1. Try:尝试执行业务,预留资源(比如锁定库存、冻结资金)。
  2. Confirm:确认执行业务,真正完成操作(比如扣减库存、扣款)。
  3. Cancel:取消执行业务(回滚 Try 阶段预留的资源)。

TCC 的核心理念是将“业务提交”与“业务补偿”显式地定义出来,由事务协调器来驱动这些操作。


TCC 的工作流程

假设我们有一个简单的转账业务:从账户 A 扣减 100 元,向账户 B 增加 100 元。

  1. Try 阶段

    • 检查账户 A 余额 ≥ 100,冻结 100 元(在数据库中增加一个冻结金额字段)。
    • 检查账户 B 有效性(不需要冻结,因为增加金额没有风险)。
    • 如果两个 Try 都成功,则进入 Confirm 阶段;如果任何一个失败,则进入 Cancel 阶段。
  2. Confirm 阶段

    • 账户 A:正式扣减 100 元(减少余额,同时清除冻结金额)。
    • 账户 B:增加 100 元。
    • Confirm 阶段不应失败(通常是幂等的,失败则重试)。
  3. Cancel 阶段

    • Try 阶段失败(A 余额不足),需要释放 A 的冻结金额。
    • Confirm 阶段失败(罕见),则通过重试或人工处理保证最终一致。

TCC 的实现要求

  1. 幂等性:Confirm 和 Cancel 操作必须支持重复调用,且结果一致(通常通过唯一事务 ID 或状态机保证)。
  2. 资源预留:Try 阶段不能直接提交业务,而是预留资源(如“冻结”、“临时记录”)。
  3. 事务协调器:需要一个中心化或分布式的协调器来记录事务状态,驱动 Try → Confirm/Cancel 流程。
  4. 补偿机制: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 等成熟框架快速接入。

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