FMT 模式 Spring Free 开发

最近更新时间: 2025-01-15 17:01:00

操作场景

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

FMT 事务,也可以理解为框架托管事务。需要用户仅需要在 FMT 规范下正常实现业务逻辑即可实现分布式事务。相对于 TCC 事务,省去了编写 Confirm、Cancel 的代码工作。 FMT 事务的实现原理:代理用户执行 PrepareStatement 和 CreateStatement 操作,对执行的 DML 语句进行解析,记录前后象,并生成 UNDO 信息。鉴于此,FMT 对用户使用的数据库和 SQL 语句会有一定的要求,详见 FMT 规范

准备工作

  • 参考 准备工作 文档,完成环境配置和开发前准备。
  • 参考 快速部署 文档,执行 FMT 初始化脚本.

开发步骤

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);

配置项说明:

配置项数据类型必填默认值描述
groupIdString用户的事务分组 ID。
txmBrokerListStringTC 集群节点列表。
secretIdString用户的 SecretID。
secretKeyString用户的 SecretKey。
serverString客户端服务标识,一个事务分组下,同一服务需要使用相同的标识。
dtf.env.fmtBooleantrue启动时会对 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");

代理数据源

对应用内的数据源(DataSource)信息进行手动代理。

DtfDataSourceProxyUtil.dataSourceProxy(String dataSourceName, DataSource dataSource);

说明:

数据源的名称(dataSourceName)需要保持稳定,并且在一个应用中不可重复。

例如:

/**
  * 代理数据源
  */
private static MysqlDataSource proxyDataSource() {
    MysqlDataSource ds = new MysqlDataSource();
    ds.setUrl(URL1);
    ds.setUser(USER1);
    ds.setPassword(PASSWORD);
    return DtfDataSourceProxyUtil.dataSourceProxy("DATA_SOURCE1", ds);
}

启用分布式事务服务

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

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

主事务管理

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

开启主事务

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

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

标识一次FMT事务

每一次数据库操作都需要进行标识。

DtfContextHolder.get().pushBranchType(DTF.BranchType.FMT);

提交/回滚主事务

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

关闭主事务上下文

说明:

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

DtfTransaction.end();

使用示例:

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

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

主调

需要从上下文中提取 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});