tcc-transaction分布式TCC型事务框架搭建与使用记录

 

首先感谢框架作者。本文只介绍框架使用,并不分析源码。

介绍:tcc-transaction是开源的TCC补偿性分布式事务框架,git地址:https://github.com/changmingxie/tcc-transaction 

TCC为Try、Confirm、Cancel的缩写:try阶段预留资源尝试提交,confirm阶段确定提交,cancel取消提交释放资源。

1.2.x项目指南地址:https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x 

 

本文的例子为:创建资产,将资产信息同时同步到Mongo与ES的流程(ES代码不列出了,与mongo类似),整个流程保证数据一致(本场景可能并不适合,看一下代码结构即可)。

 

代码与流程:

1.下载1.2.x版本源码,并可能需要修改部分代码

因为是第三方包,所以需要自己打包到本地仓库。但包中spring版本为3.2.12.RELEASE,如果本地项目为4.x,比如本人的项目spring版本为4.3.4.RELEASE,如果不修改tcc中的spring版本,将报错无法启动。

源码修改比较简单,如下:

1.1 修改tcc-transaction总pom.xml文件 

 


4.3.4.RELEASE



      org.quartz-scheduler
      quartz
      2.2.1
      
          
              c3p0
              c3p0
          
      




       com.alibaba
       dubbo
       2.5.3

 

 

 

1.2 修改 tcc-transaction-spring/src/main/java/org/mengyun/tcctransaction/spring/recover/RecoverScheduledJob.java 

该文件中 CronTriggerBean类在4.x中已经不存在,也是修改源码主要修改的地方。

修改其中的init方法,修改后如下:

 

public void init() {

        try {
            MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
            jobDetail.setTargetObject(transactionRecovery);
            jobDetail.setTargetMethod("startRecover");
            jobDetail.setName("transactionRecoveryJob");
            jobDetail.setConcurrent(false);
            jobDetail.afterPropertiesSet();

            CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
            cronTrigger.setBeanName("transactionRecoveryCronTrigger");
            cronTrigger.setJobDetail(jobDetail.getObject());

            cronTrigger.setCronExpression(transactionConfigurator.getRecoverConfig().getCronExpression());
            cronTrigger.afterPropertiesSet();

            scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject());

            scheduler.start();

        } catch (Exception e) {
            throw new SystemException(e);
        }
    }

也可参考如下的修改:https://github.com/changmingxie/tcc-transaction/pull/84/files 

 

 

1.3 打包并发布

mvn -Dmaven.test.skip=true install

 

2.项目依赖

参考1.2.x使用指南,引入两个依赖(本人项目使用SOA架构dubbo框架,我使用并打包时版本为1.2.3.1)。调用方和提供方都需要引入依赖。

 

3.加载tcc-transaction.xml配置

原文中是配置在web.xml中,我个人试了一下放在dubbo web项目的web.xml中,但配置并没有被加载。该文件的意义只是希望项目启动时被加载,于是直接在dubbo中的一个spring的配置文件中引入,如下:

 


该文件里面提供各种aop逻辑,项目启动时扫描指定注解,并做增强。

 

 

4.设置TransactionRepository

需要为tcc配置数据源,可以是mysql或其他nosql,本文使用msyql,其他可参见原指南文档。

mysql配置如下:

 



	
	
	
	
	
	
	
	
	
	
	
	



	
	
	



	
	
	
	
		
			com.alibaba.dubbo.remoting.TimeoutException
		
	

需要注意的点:1.数据源必须配置新的,不能使用之前项目存在的dataSource的bean,也不能在同一库中,不然会导致tcc表数据与本地事务一起回滚,从而无法保存异常事务日志;2.注意domain、tbSuffix的配置,这两项文档中并没有配置,但源码demo中配置了,用于数据库的表名称等,推荐配置;3.最后的DefaultRecoverConfig项是可选的,用于恢复与重试,具体作用参考使用指南;4.defaultAutoCommit必须为true(默认为true)

 

 

5.mysql建表脚本

根据以上的tbSufifix配置,脚本如下:

 

CREATE TABLE `tcc_transaction_asset` (
  `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT,
  `DOMAIN` varchar(100) DEFAULT NULL,
  `GLOBAL_TX_ID` varbinary(32) NOT NULL,
  `BRANCH_QUALIFIER` varbinary(32) NOT NULL,
  `CONTENT` varbinary(8000) DEFAULT NULL,
  `STATUS` int(11) DEFAULT NULL,
  `TRANSACTION_TYPE` int(11) DEFAULT NULL,
  `RETRIED_COUNT` int(11) DEFAULT NULL,
  `CREATE_TIME` datetime DEFAULT NULL,
  `LAST_UPDATE_TIME` datetime DEFAULT NULL,
  `VERSION` int(11) DEFAULT NULL,
  PRIMARY KEY (`TRANSACTION_ID`),
  UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


如果表名称不对,启动过程会报错,做相应调整即可。

 

 

6.发布服务(重点)

6.1 dubbo接口

 

public interface AssetCardService {

    /**
     * 测试预保存资产(状态为待确认)
     */
    @Compensable
    int testSaveAssetCard(AssetCardModel model);

    /**
     * 确认保存资产到mysql(状态为正常)
     */
    int confirmMysqlSaveAssetCard(AssetCardModel model);

    /**
     * 取消保存资产到msyql(更新状态为删除)
     */
    int cancelMysqlSaveAssetCard(AssetCardModel model);

    /**
     * 预保存资产到mongo(状态为待确认)
     */
    @Compensable
    void processMongo(AssetCardModel model);

    /**
     * 确认保存资产到mongo(状态为正常)
     */
    void confirmMongoSaveAssetCard(AssetCardModel model);

    /**
     * 取消保存资产到mongo(更新状态为删除)
     */
    void cancelMongoSaveAssetCard(AssetCardModel model);

}


需要注意的点:1.对外提供服务的接口必须有@Compensable注解;2.对应的confirm与cancel方法必须声明为接口,不能声明为private,即使是public也不行,必须有接口。

 

 

6.2 dubbo接口实现类

 

    @Override
    @Compensable(confirmMethod = "confirmMysqlSaveAssetCard", cancelMethod = "cancelMysqlSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
    public int testSaveAssetCard(AssetCardModel model){

        // 保存mysql,data状态为-1
        model.setDataStatus(-1);
        assetCardDao.insert(model);


        // mongo处理
        assetCardService.processMongo(model);

        return model.getId();
    }

    @Override
    public int confirmMysqlSaveAssetCard(AssetCardModel model){
        System.out.println("============================================================================");
        System.out.println("=================mysql:confirm");
        System.out.println("============================================================================");

        // 更新mysql data_status为0
        model.setDataStatus(0);
        assetCardDao.updateByPrimaryKey(model);

        return model.getId();
    }

    @Override
    public int cancelMysqlSaveAssetCard(AssetCardModel model){
        System.out.println("============================================================================");
        System.out.println("=================mysql:cancel");
        System.out.println("============================================================================");

        // 更新mysql data_status为-1
        model.setDataStatus(-1);
        assetCardDao.updateByPrimaryKey(model);

        return model.getId();
    }

    @Compensable(confirmMethod = "confirmMongoSaveAssetCard", cancelMethod = "cancelMongoSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)
    @Override
    public void processMongo(AssetCardModel model) {

        // 保存mongo,data_statu为-1
        model.setDataStatus(-1);
        assetCardDaoWrapper.saveMongo(model);
    }

    @Override
    public void confirmMongoSaveAssetCard(AssetCardModel model){
        System.out.println("============================================================================");
        System.out.println("=================mongo:confirm");
        System.out.println("============================================================================");

        // 更新mongo data_status为0
        model.setDataStatus(0);
        assetCardDaoWrapper.updateMongo(model);
    }

    @Override
    public void cancelMongoSaveAssetCard(AssetCardModel model){
        System.out.println("============================================================================");
        System.out.println("=================mongo:cancel");
        System.out.println("============================================================================");

        // 更新mongo data_status为-1
        model.setDataStatus(-1);
        assetCardDao.updateByPrimaryKey(model);
        assetCardDaoWrapper.updateMongo(model);
    }

 

 

说明与注意点:1.对外提供服务的接口必须有@Compensable注解,同时必须有confirmMethod、cancelMethod参数的配置,同时dubbo接口额外增加 "transactionContextEditor = DubboTransactionContextEditor.class"这个配置;2.提供服务接口与对应另外的两个CC方法参数必须完全一致;3.该tcc框架可嵌套调用,如上在testSaveAssetCard方法,即try阶段中调用了另一个tcc方法"assetCardService.processMongo()",理论上嵌套只应该在try阶段进行;4.confirm、cancel需要实现幂等性,可能会被重试;5.由于网络等因素,可能导致cancel方法先执行,cancel方法一定要做好相应的判断与处理

 

6.3 调用方

 

 

@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
public long testSaveAssetCard(AssetCardModel assetCardModel) throws AssetException {
    
    assetCardModel.setId(IdGenerator.getId());  
    return assetCardService.testSaveAssetCard(assetCardModel);
}

注意点:1.因为需要回滚更新等操作,所以此业务中id不能用自增,而是需要项目生成;2.特别注意,调用方必须在事务中,也就是说必须有事务注解,或者能被事务配置切到,没有事务tcc框架调用时会抛异常。

 

至此,配置已经全部完成。

 

7.事务查看

源码中提供tcc-transaction-server web项目,该项目提供界面查看事务日志,修改里面配置文件,打包后部署即可,不再赘述。

 

备:

业务流程使用记录:

前提:用户下单,建立订单,创建支付记录,支付记录状态为待支付

try:

    用户金额冻结

    调用积分处理TCC:

    try:预增加积分

    confirm:更新增加积分状态

    cancel:取消增加的积分

 confirm:

    订单支付状态更新为已支付

    订单支付记录支付状态更新为已支付 

    用户金额扣款(以上三个操作在同一本地事务)

cancel:

    判断订单支付状态与订单记录支付状态为未支付
    用户冻结金额释放
 

 

来自2019年初的后续:该框架侵入性比较大,使代码变的繁重,优点不再表。期待Fescar的成熟。

你可能感兴趣的:(架构设计)