FMT 模式参与者

分布式事务支持以 FMT 模式接入 SOFARPC 和 Dubbo 远程服务框架。

本文将介绍如何:

FMT 模式参与者只需要替换数据源,即可将对该数据源的访问纳入到分布式事务中。一个数据源就是一个参与者。

配置数据源

您可以选择以下任一数据源配置方案:

    封装数据源

    您可以选择使用自选的数据源,然后使用分布式事务数据源代理(com.alipay.sofa.dtx.datasource.WrappedDtxDataSource)封装该数据源,配置示例如下:

    原代码

    <!-- 自选的数据源,可选择任何类型 DataSource -->
    <bean id="xxxDataSource" class="com.xxx.xxx.XDataSource">
    <!-- 此处为自选数据源的各种参数配置 -->
    </bean>

    修改为

    <bean id="xxxDataSource" class="com.alipay.sofa.dtx.datasource.WrappedDtxDataSource">
         <property name="targetDataSource" ref="xxxDataSource_inner"/>
         <property name="uniqueDbId" value="[数据源全局唯一的 ID]"/>
         <property name="dbType" value="[数据源类型]"/>
    </bean> 

    业务应用访问数据库时,需要使用代理后的 xxxDataSource 作为 DataSource Bean。

    分布式事务代理数据源 WrappedDtxDataSource 属性介绍:

    • targetDataSource:指定用户自选数据源 Bean。

    • uniqueDbId:事务参与方标识,该标识应该是全局唯一的(与 TCC 模式注解 name 属性值作用相同)。

    • dbType:表示数据源类型,支持 MySQL、Oracle 以及 OceanBase。

    替换数据源

    您可以选择直接使用分布式事务提供的数据源(com.alipay.sofa.dtx.datasource.druid.DruidDtxDataSource),配置示例如下:

    原代码

    <!-- 自选的数据源,可选择任何类型 DataSource -->
    <bean id="xxxDataSource" class="com.xxx.xxx.XDataSource">
    <!-- 此处为自选数据源的各种参数配置 -->
    </bean>

    修改为

    <bean id="xxxDataSource" class="com.alipay.sofa.dtx.datasource.druid.DruidDtxDataSource">
         <property name="jdbcUrl" value="${jdbcUrl}"/>
         <property name="userName" value="${jdbc userName}"/>
         <property name="passWord" value="${jdbc passWord]}"/>
         <property name="uniqueDbId" value="[数据源全局唯一的ID]"/>
         <property name="dbType" value="[数据源类型]"/>
    </bean>
    重要

    分布式事务数据源完全兼容 Druid 数据源,如果是其他数据源,可能需要修改注入参数。

    分布式事务数据源 DruidDtxDataSource 属性介绍

    必填属性

    参数

    说明

    jdbcUrl

    属性指定数据库的 JDBC URL。

    userName

    属性指定访问数据库的用户名。

    passWord

    属性指定访问数据库的用户密码。

    uniqueDbId

    属性表示数据源的唯一 ID,需要全局唯一,由用户自定义。

    dbType

    属性指定数据源类型,MySQL、Oracle 或 OceanBase。

    可选属性

    参数

    说明

    initialSize

    数据库链接池初始连接数。

    minIdle

    数据库链接池最小连接数。

    maxActive

    数据库链接池最大连接数。

    poolPreparedStatements

    是否缓存 preparedStatement。

    maxOpenPreparedStatements

    要启用 poolPreparedStatements,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。

    maxWait

    获取连接时最大等待时间,单位为毫秒。

    validationQuery

    用来检测连接是否有效的 SQL,要求是一个查询语句。如果 validationQuery 为 NULL,那么 testOnBorrowtestOnReturntestWhileIdle 都不会起作用。

    testOnBorrow

    申请连接时执行 validationQuery 检测连接是否有效。

    testOnReturn

    归还连接时执行 validationQuery 检测连接是否有效。

    testWhileIdle

    如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。

    timeBetweenEvictionRunsMillis

    Destroy 线程会检测连接的间隔时间,以 testWhileIdle 的判断为依据,详见 testWhileIdle 属性说明。

    创建数据表

    FMT 模式需要在数据库中记录事务信息,所以您需要在与上述配置相同的数据库中创建分布式事务的快照数据(dtx_branch_info)和保存行锁的表(dtx_row_lock)。

    如果业务库已分库分表,您只需在每一个库中分别创建一张分布式事务的表即可,这些表不需要拆分。

    根据不同的数据库类型,可参考 更多信息 中的 MySQL、OceanBase、Oracle 建表语句。

    发布服务

    分布式事务内可能会操作多个数据库,这些数据库的访问服务由多个不同的应用提供,即跨服务 FMT 参与者。这些应用内使用了 FMT 模式参与者后,需要将包含 DAO 操作的方法发布成 SOFARPC 服务或 Dubbo 服务。这样就可以被分布式事务的发起方进行调用,来保证分布式场景下的数据一致性。

    假设该应用数据访问服务实现如下:

    public class DataServiceImpl implements com.xxx.DataService{
    //服务实现
    }

    基于 SOFARPC 的服务发布

    如果需要发布为 SOFARPC 服务,服务发布代码如下:

    <bean id="dataServiceImpl" class="com.xxx.DataServiceImpl"/>
    
    <sofa:service ref="dataServiceImpl" interface=com.xxx.DataService" >
          <sofa:binding.tr/>
    </sofa:service>

    基于 Dubbo 的服务发布

    如果需要发布为 Dubbo 服务,服务发布代码如下:

    <bean id="dataServiceImpl" class="com.xxx.DataServiceImpl"/>
    
    <dubbo:service ref="dataServiceImpl" interface=com.xxx.DataService" />

    更多信息

    MySQL、OceanBase、Oracle 建表语句

    • MySQL

      记录锁表:

      CREATE TABLE `dtx_row_lock`(
      `action_id` varchar(128) NOT NULL COMMENT '分支事务号',
      `tx_id` varchar(128) NOT NULL COMMENT '主事务号',
      `table_name` varchar(64) DEFAULT NULL COMMENT '表名称',
      `row_key` varchar(250) NOT NULL COMMENT '行唯一key',
      `instance_id` varchar(32) NOT NULL COMMENT '实例id',
      `context` varchar(2000) DEFAULT NULL COMMENT '分支上下文',
      `feature` varchar(2000) DEFAULT NULL COMMENT '扩展属性',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '修改时间',
      PRIMARY KEY (`row_key`),
      KEY `idx_row_lock_txid_action_id`(`action_id`,`tx_id`,`instance_id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='行锁'
      ;
      重要

      原生 MySQL 中需要修改 CHARSET=latin1。MySQL 每个单表中所创建的索引长度是有限制的,Innodb 引擎的长度不超过 767 字节。 row_key varchar(250) 表示 250 个字符,字符集使用的 utf8,可能会导致超过限制。

      分支事务记录表:

      CREATE TABLE `dtx_branch_info`(
      `action_id` varchar(128) NOT NULL COMMENT '分支事务号',
      `tx_id` varchar(128)  NOT NULL  COMMENT '主事务号',
      `status` varchar(4)  COMMENT '事务状态',
      `log_info` blob DEFAULT NULL COMMENT 'undo/redo log',
      `biz_type` varchar(32) DEFAULT NULL COMMENT '发起方业务类型',
      `instance_id` varchar(32) NOT NULL COMMENT '实例id',
      `context` varchar(2000) DEFAULT NULL COMMENT '分支上下文',
      `feature` varchar(2000) DEFAULT NULL COMMENT '扩展属性',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '修改时间',
         PRIMARY KEY (`action_id`),
         KEY `idx_txId_action_id`(`action_id`,`tx_id`,`instance_id`)
      ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='分支记录日志'
      ;
    • OceanBase v1.0

      记录锁表:

      CREATE TABLE `dtx_row_lock`(
      `action_id` varchar(128) NOT NULL COMMENT '分支事务号',
      `tx_id` varchar(128) NOT NULL COMMENT '主事务号',
      `table_name` varchar(64) DEFAULT NULL COMMENT '表名称',
      `row_key` varchar(250) NOT NULL COMMENT '行唯一key',
      `instance_id` varchar(32) NOT NULL COMMENT '实例id',
      `context` varchar(2000) DEFAULT NULL COMMENT '分支上下文',
      `feature` varchar(2000) DEFAULT NULL COMMENT '扩展属性',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '修改时间',
      PRIMARY KEY (`row_key`),
      KEY `idx_row_lock_txid_action_id`(`action_id`,`tx_id`,`instance_id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='行锁'
      ;

      分支事务记录表:

      CREATE TABLE `dtx_branch_info`(
      `action_id` varchar(128) NOT NULL COMMENT '分支事务号',
      `tx_id` varchar(128)  NOT NULL  COMMENT '主事务号',
      `status` varchar(4)  COMMENT '事务状态',
      `log_info` varchar(32768) DEFAULT NULL COMMENT 'undo/redo log',
      `biz_type` varchar(32) DEFAULT NULL COMMENT '发起方业务类型',
      `instance_id` varchar(32) NOT NULL COMMENT '实例id',
      `context` varchar(2000) DEFAULT NULL COMMENT '分支上下文',
      `feature` varchar(2000) DEFAULT NULL COMMENT '扩展属性',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '修改时间',
         PRIMARY KEY (`action_id`),
         KEY `idx_txId_action_id`(`action_id`,`tx_id`,`instance_id`)
      ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='分支记录日志'
      ;
    • Oracle

      记录锁表:

      CREATE TABLE dtx_row_lock(
        action_id varchar2(64) NOT NULL,
        tx_id varchar2(128) NOT NULL,
        table_name varchar2(64) DEFAULT NULL,
        row_key varchar2(250) PRIMARY KEY,
        instance_id varchar2(32) NOT NULL,
        context varchar2(2000) DEFAULT NULL,
        feature varchar2(2000) DEFAULT NULL,
        gmt_create timestamp NOT NULL,
        gmt_modified timestamp NOT NULL 
      );
      CREATE INDEX idx_row_lock_txid_action_id ON dtx_row_lock(action_id , tx_id , instance_id);

      分支事务记录表:

      CREATE TABLE dtx_branch_info (
         action_id varchar2(64) PRIMARY KEY,
         tx_id varchar2(128) NOT NULL,
         status varchar2(4),
         log_info blob DEFAULT NULL,
         biz_type varchar2(32) DEFAULT NULL,
         instance_id varchar2(32) NOT NULL,
         context varchar2(2000) DEFAULT NULL,
         feature varchar2(2000) DEFAULT NULL,
         gmt_create timestamp NOT NULL,
         gmt_modified timestamp NOT NULL 
      );
      CREATE INDEX idx_txId_action_id ON dtx_branch_info(action_id , tx_id , instance_id);