TCC 模式 Spring Free 开发

最近更新时间: 2026-06-30 15:06:00

操作场景

该任务指导您在 TCC 模式下进行 Spring Free 开发。

TCC 事务,也可以理解为手动事务。需要用户提供 Try、Confirm、Cancel 接口并进行实现,同时需要保证三个接口的幂等性。

准备工作

参考 准备工作 文档。

开发步骤

Maven 配置

通过配置业务代码的 pom.xml 文件,可以引入 DTF 的 SDK 到您的工程中。

<dependency>
    <groupId>com.tencent.cloud</groupId>
    <artifactId>dtf-core</artifactId>
    <version>${dtf.version}</version>
</dependency>

客户端配置

在客户端中,使用以下 API 进行 DTF 的配置设置。

DtfEnv.setServer(String server);
DtfEnv.setSecretId(String secretId);
DtfEnv.setSecretKey(String secretKey);
DtfEnv.addTxmBroker(String groupId, String txmBrokerList);

配置项说明:

配置项 数据类型 必填 默认值 描述
groupId String 是 无 用户的事务分组 ID。
txmBrokerList String 是 无 TC 集群节点列表。
secretId String 是 无 用户的TCloudFinanceZone SecretID。
secretKey String 是 无 用户的TCloudFinanceZone SecretKey。
server String 是 无 客户端服务标识,一个事务分组下,同一服务需要使用相同的标识。
dtf.env.fmt Boolean 否 true 启动时会对 DB 进行大量初始化工作,若不需使用 fmt 建议禁用。

通常情况下,仅需要在使用DtfEnv.addTxmBroker()配置一个事务分组。例如:
用户 A,创建了一个事务分组group-x3k9s0ns,在 分布式事务控制台 获取该分组的 TC 集群地址为127.0.0.1:8080;127.0.0.1:8081;127.0.0.1:8082。该用户访问密钥的 SecretId 为SID,SecretKey 为SKEY。需要在业务应用app-test上使用该事物时,配置样例为:

DtfEnv.setServer("app-test");
DtfEnv.setSecretId("SID");
DtfEnv.setSecretKey("SKEY");
DtfEnv.addTxmBroker("group-x3k9s0ns", "127.0.0.1:8080;127.0.0.1:8081;127.0.0.1:8082");

注册 TCC

对应用内的 TCC 信息进行手动注册。

// 获取 TCC 实例
TCC.getInstance(Object confirmClassInstance, String confirmMethodName, Object cancelClassInstance,
                String cancelMethodName);
// 或
TCC.getInstance(Object confirmClassInstance, Method confirmMethod, Object cancelClassInstance,
                Method cancelMethod);
// 注册 TCC 信息
TccRegistry.register(String tccName, TCC tccInstance);

注意:TCC 的名称(tccName)需要保持稳定,并且在一个应用中不可重复。

例如:

/**
  * 注册 TCC 信息
  */
private static void registerTcc() {
    ITransferService transferService = new TransferService();
    // debit 的 Confirm 和 Cancel
    TccRegistry.register("debit",
            TCC.getInstance(transferService, "confirmDebit", transferService, "cancelDebit"));
    // credit 的 Confirm 和 Cancel
    TccRegistry.register("credit",
            TCC.getInstance(transferService, "confirmCredit", transferService, "cancelCredit"));
}

示例中注册了两个 TCC:debit(扣款)和credit(入账)。

debit中参数如下:

参数 说明
confirmClassInstance TransferService 的实例 transferService
confirmMethodName TransferService中 名称为 confirmDebit 的方法
cancelClassInstance TransferService 的实例 transferService
cancelMethodName TransferService 中名称为 cancelDebit

credit中参数如下:

参数 说明
confirmClassInstance TransferService 的实例 transferService
confirmMethodName TransferService 中名称为 confirmCredit 的方法
cancelClassInstance TransferService 的实例 transferService
cancelMethodName TransferService 中名称为 cancelCredit 的方法

启用分布式事务服务

在用户应用处理逻辑完成,并且完成客户端配置和注册 TCC步骤后,可以使用 API 来开启分布式事务服务。

// 启动分布式事务客户端
DtfClient.start();

主事务管理

主事务的生命周期可以分为:开启、提交/回滚。

开启主事务

在客户端配置步骤中只添加了一个事务分组时,可以使用开启主事务:使用默认事务分组。添加了多个事务分组时,必须使用开启主事务:使用指定事务分组。

// 开启主事务:使用默认事务分组
DtfTransaction.begin(Integer timeout);
// 开启主事务:使用指定事务分组
DtfTransaction.begin(Integer timeout, String groupId)

提交/回滚主事务

// 提交主事务
DtfTransaction.commit();
// 回滚主事务
DtfTransaction.rollback();

关闭主事务上下文

注意:该操作仅关闭当前线程的主事务上下文,不会对主事务状态产生影响。

DtfTransaction.end();

使用示例:

public boolean execute() {
    // 业务检查
    Long txId = DtfTransaction.begin(10000);
    try {
        // 执行各个分支事务或业务逻辑
        // 提交主事务
        DtfTransaction.commit();
        return true;
    } catch (Throwable e) {
        // 回滚主事务
        DtfTransaction.rollback();
        LOG.error("Bank transfer failed.", e);
        return false;
    } finally {
        // 关闭主事务上下文
        DtfTransaction.end();
    }
}

分支事务管理

分支事务的生命周期可以分为:开启、提交/回滚。

一个 TCC 分支事务中,需要包含 Try、Confirm、Cancel 三个部分。

  • 分支事务的 Try、Confirm、Cancel 方法的参数保持一致。
  • 分支事务的 Try、Confirm、Cancel 方法的前两个参数固定为Long txId和Long branchId。
    Try 方法:
  • 本地调用 Try 方法时txId和branchId参数传null,其他参数正常传递。
  • 返回值为业务逻辑需要的返回值。
    Confirm 方法:
  • 返回值固定为 Boolean 类型。
  • 仅在返回 true 时视为分支事务 Confirm 成功。
  • 返回 false 或抛出异常时,视为分支事务 Confirm 失败。
    Cancel 方法:
  • 返回值固定为 Boolean 类型。
  • 仅在返回 true 时视为分支事务 Cancel 成功。
  • 返回 false 或抛出异常时,视为分支事务 Cancel 失败。

开启分支事务

DtfTccBranch.begin(String name, Object[] params);
参数 说明
name TCC 名称
params Try 方法的业务参数,前两个参数(即Long txId和Long branchId) 填null

检查主事务状态是否为 Trying

仅在 Trying 状态时允许提交分支事务,该接口主要用于防止分支事务 Try 阶段延迟提交本地事务。

DtfTccBranch.checkTxTrying();

结束分支事务

注意:该操作仅关闭当前线程的分支事务上下文,不会对分支事务状态产生影响。

DtfTccBranch.end();

示例:

// === 开启主事务 ===

// 开启分支事务1:扣款
Long branchId1 = DtfTccBranch.begin("debit", new Object[] { null, null, this.to, this.amount });
// 执行Try方法1
transferService.debit(txId, branchId1, this.to, this.amount);
// 关闭分支事务1上下文
DtfTccBranch.end();

// 开启分支事务2:入账
Long branchId2 = DtfTccBranch.begin("credit", new Object[] { null, null, this.from, this.amount });
// 执行Try方法2
transferService.credit(txId, branchId2, this.from, this.amount);
// 关闭分支事务2上下文
DtfTccBranch.end();

// === 提交 / 回滚主事务 ===

说明:在执行 Try 方法的本地事务末尾,需要使用DtfTccBranch.checkTxTrying();接口防止 Try 阶段延迟提交本地事务。

远程请求时传递分布式事务上下文

主调

需要从上下文中提取groupId,txId,lastBranchId三个数据传递到下游。

使用以下 API 提取:

String groupId = DtfContextHolder.get().getGroupId();
Long txId = DtfContextHolder.get().getTxId();
Long lastBranchId = DtfContextHolder.get().getBranchIdStack().peek();

建议放到下列 Header 的 key 中,下游可以通过 DTF SDK 自行注入。

# Header key的常量ClientConstant.HTTP_HEADER.GROUP_ID
DTF-Group-ID: ${GroupId}
# Header key的常量ClientConstant.HTTP_HEADER.TX_ID
DTF-Tx-ID: ${TxId}
# Header key的常量 ClientConstant.HTTP_HEADER.LAST_BRANCH_ID
DTF-Last-Branch-ID: ${LastBranchId}

被调

根据上游业务的特性手动获取groupId,txId,lastBranchId三个数据。

如果上游使用的是 DTF 封装的 RestTemplate 或 Fegin,请从以下请求头中获取:

# Header key的常量ClientConstant.HTTP_HEADER.GROUP_ID
DTF-Group-ID: ${GroupId}
# Header key的常量ClientConstant.HTTP_HEADER.TX_ID
DTF-Tx-ID: ${TxId}
# Header key的常量 ClientConstant.HTTP_HEADER.LAST_BRANCH_ID
DTF-Last-Branch-ID: ${LastBranchId}

如果是其他方式传递,则需要用户按照接口协议自行获取。

获取了三个数据后,通过 API 绑定分布式事务上下文。

DtfTransaction.bind(${GroupId}, ${TxId}, ${LastBranchId});