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  -->
<springframework.version>4.3.4.RELEASE</springframework.version>

<!-- 第二處:修改版本爲2.2.1  -->
<dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.2.1</version>
      <exclusions>
          <exclusion>
              <groupId>c3p0</groupId>
              <artifactId>c3p0</artifactId>
          </exclusion>
      </exclusions>
</dependency>

<!-- 第三處:修改版本爲2.5.3  -->
<dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>dubbo</artifactId>
       <version>2.5.3</version>
</dependency>

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的配置文件中引入,如下:

<!-- TCC Transaction -->
<import resource="classpath:tcc-transaction.xml" />
該文件裏面提供各種aop邏輯,項目啓動時掃描指定註解,並做增強。


4.設置TransactionRepository

需要爲tcc配置數據源,可以是mysql或其他nosql,本文使用msyql,其他可參見原指南文檔。

mysql配置如下:

<!--tcc-->
<bean id="tccDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="${jdbc.driverClassName}" />
	<property name="url" value="${jdbc.tcc.url}" />
	<property name="username" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />
	<property name="initialSize" value="${dbcp.initialSize}" />
	<property name="maxActive" value="${dbcp.maxActive}" />
	<property name="maxIdle" value="${dbcp.maxIdle}" />
	<property name="maxWait" value="${dbcp.maxWait}" />
	<property name="poolPreparedStatements" value="${dbcp.poolPreparedStatements}" />
	<property name="defaultAutoCommit" value="${dbcp.defaultAutoCommit}" />
	<property name="timeBetweenEvictionRunsMillis" value="${dbcp.timeBetweenEvictionRunsMillis}" />
	<property name="minEvictableIdleTimeMillis" value="${dbcp.minEvictableIdleTimeMillis}" />
</bean>

<bean id="transactionRepository"
	  class="org.mengyun.tcctransaction.spring.repository.SpringJdbcTransactionRepository">
	<property name="dataSource" ref="tccDataSource"/>
	<property name="domain" value="SAAS"/>
	<property name="tbSuffix" value="_ASSET"/>
</bean>

<bean class="org.mengyun.tcctransaction.spring.recover.DefaultRecoverConfig">
	<property name="maxRetryCount" value="30"/>
	<property name="recoverDuration" value="120"/>
	<property name="cronExpression" value="0 */1 * * * ?"/>
	<property name="delayCancelExceptions">
		<util:set>
			<value>com.alibaba.dubbo.remoting.TimeoutException</value>
		</util:set>
	</property>
</bean>
需要注意的點: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:

    判斷訂單支付狀態與訂單記錄支付狀態爲未支付
    用戶凍結金額釋放

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章