TCC 模式 Spring Free 开发

最近更新时间: 2024-06-12 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 用户的腾讯云金融专区 SecretID。
secretKey String 用户的腾讯云金融专区 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 txIdLong branchId

Try 方法

  • 本地调用 Try 方法时txIdbranchId参数传null,其他参数正常传递。
  • 返回值为业务逻辑需要的返回值。

Confirm 方法

  • 返回值固定为 Boolean 类型。
  • 仅在返回 true 时视为分支事务 Confirm 成功。
  • 返回 false 或抛出异常时,视为分支事务 Confirm 失败。

Cancel 方法

  • 返回值固定为 Boolean 类型。
  • 仅在返回 true 时视为分支事务 Cancel 成功。
  • 返回 false 或抛出异常时,视为分支事务 Cancel 失败。

开启分支事务

DtfTccBranch.begin(String name, Object[] params);
参数 说明
name TCC 名称
params Try 方法的业务参数,前两个参数(即Long txIdLong 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 阶段延迟提交本地事务。

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

主调

需要从上下文中提取groupIdtxIdlastBranchId三个数据传递到下游。

使用以下 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}

被调

根据上游业务的特性手动获取groupIdtxIdlastBranchId三个数据。

如果上游使用的是 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});