一、背景

有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了能够快速迭代上线,往往不太注重产品/项目/系统的高可靠性、高性能与高扩展性,采用单体应用和单实例数据库的架构方式快速迭代开发;当产品/项目/系统做到一定规模的时候,原有的系统架构则不足以支撑义务发展需要,往往相同的业务则需要重复写很多次,导致代码大量冗余,难以维护和扩展,这时不得不对原有产品/项目/系统进行拆分,引入分布式的系统架构;而对原有产品/项目/系统进行拆分的过程中,对于业务和数据的拆分和迁移则成为了最为棘手的问题,尤其是在原有业务不能下线,拆分后的业务同时上线的场景下这种问题更加突出;项目拆分后,业务被拆分为多个独立的子业务分散到多个子系统中,而原有的单一数据库则被拆分到多个数据库中,拆分后的数据库则同样又面临着让人头疼的分布式事务的问题。

本文就针对项目拆分后数据库的分布式事务问题,基于tcc-transaction分布式TCC型事务进行框架的搭建,同时引入相关的实战案例,来解决让人头疼的分布式事务问题。

二、tcc-transaction框架介绍

介绍: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 修改 Java类

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 打包并发布

这里我们通过Maven进行打包发布,命令为:

mvn -Dmaven.test.skip=true install

2.项目依赖

参考1.2.x使用指南,引入两个依赖(本人项目dubbo/dubbox框架,我使用并打包时版本为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,本文使用mysql,其他可参见原指南文档。
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接口
 /*** @author binghe* 资产相关的业务发布Dubbo服务*/
public interface AssetCardService {/*** 测试预保存资产(状态为待确认)*/@Compensableint testSaveAssetCard(AssetCardModel model);/*** 确认保存资产到mysql(状态为正常)*/int confirmMysqlSaveAssetCard(AssetCardModel model);/*** 取消保存资产到msyql(更新状态为删除)*/int cancelMysqlSaveAssetCard(AssetCardModel model);/*** 预保存资产到mongo(状态为待确认)*/@Compensablevoid processMongo(AssetCardModel model);/*** 确认保存资产到mongo(状态为正常)*/void confirmMongoSaveAssetCard(AssetCardModel model);/*** 取消保存资产到mongo(更新状态为删除)*/void cancelMongoSaveAssetCard(AssetCardModel model);}

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

6.2 dubbo接口实现类
/**
* @author binghe
* 资产相关的业务发布Dubbo服务的实现
*/
@Service
@Component
public class AssetCardServiceImpl implements AssetCardService{@Override@Compensable(confirmMethod = "confirmMysqlSaveAssetCard", cancelMethod = "cancelMysqlSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })public int testSaveAssetCard(AssetCardModel model){// 保存mysql,data状态为-1model.setDataStatus(-1);assetCardDao.insert(model);// mongo处理assetCardService.processMongo(model);return model.getId();}@Overridepublic int confirmMysqlSaveAssetCard(AssetCardModel model){System.out.println("============================================================================");System.out.println("=================mysql:confirm");System.out.println("============================================================================");// 更新mysql data_status为0model.setDataStatus(0);assetCardDao.updateByPrimaryKey(model);return model.getId();}@Overridepublic int cancelMysqlSaveAssetCard(AssetCardModel model){System.out.println("============================================================================");System.out.println("=================mysql:cancel");System.out.println("============================================================================");// 更新mysql data_status为-1model.setDataStatus(-1);assetCardDao.updateByPrimaryKey(model);return model.getId();}@Compensable(confirmMethod = "confirmMongoSaveAssetCard", cancelMethod = "cancelMongoSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)@Overridepublic void processMongo(AssetCardModel model) {// 保存mongo,data_statu为-1model.setDataStatus(-1);assetCardDaoWrapper.saveMongo(model);}@Overridepublic void confirmMongoSaveAssetCard(AssetCardModel model){System.out.println("============================================================================");System.out.println("=================mongo:confirm");System.out.println("============================================================================");// 更新mongo data_status为0model.setDataStatus(0);assetCardDaoWrapper.updateMongo(model);}@Overridepublic void cancelMongoSaveAssetCard(AssetCardModel model){System.out.println("============================================================================");System.out.println("=================mongo:cancel");System.out.println("============================================================================");// 更新mongo data_status为-1model.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项目,该项目提供界面查看事务日志,打包后部署即可,我们这里就不在作详细的描述。

四、TCC执行流程

业务流程使用记录:

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

try:

用户金额冻结

调用积分处理TCC:

try:预增加积分

confirm:更新增加积分状态

cancel:取消增加的积分

confirm:

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

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

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

cancel:

判断订单支付状态与订单记录支付状态为未支付

用户冻结金额释放

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

【分布式事务】tcc-transaction分布式TCC型事务框架搭建与实战案例(基于Dubbo/Dubbox)...相关推荐

  1. 我说分布式事务之最大努力通知型事务

    来源:http://t.cn/E4ejkSN 在之前的文章中,我们介绍了基于TCC模式的分布式事务解决方案 我说分布式事务之TCC . TCC适用于公司内部对一致性.实时性要求较高的业务场景,而本文我 ...

  2. 分布式事务(三)、柔性事务之 TCC、Saga、本地消息表、事务消息、最大努力通知

    目录 [前言] TCC Saga [通知型事务] 本地消息表 MQ事务消息 最大努力通知 总结 分布式事务: 分布式事务(一).CAP,BASE理论 分布式事务(二).刚性事务之 2PC.3PC [前 ...

  3. shardingjdbc (九)-最大努力型事务

    一 序: Sharding-JDBC由于性能方面的考量,决定不支持强一致性分布式事务.目前支持的: Best efforts delivery transaction (已经实现). Try conf ...

  4. 【死磕Sharding-jdbc】—–最大努力型事务

    2019独角兽企业重金招聘Python工程师标准>>> BASE Transaction Best efforts delivery transaction (已经实现). Try ...

  5. mysql事务变量_mysql学习四之事务、变量、触发器、函数、存储过程

    需求:有一张银行账户表,有A用户给B用户转账:A账户先减少,B账户增加,但是A操作完之后断电了. 解决方案:A减少钱,但是不要立即修改数据表:B收到钱之后,同时修改数据表 事务安全 事务:transa ...

  6. 分布式事务:XA,2PC,3PC,TCC,SEATA(AT)

    一.分布式事务产生原因 1.原本的数据是单库单表存储,随着业务的不断扩大数据量不断增多,单库性能支撑不了数据的更新与访问.为了解决数据库上的瓶颈,将数据库进行水平拆分,原来一个库里的事务操作,现在变成 ...

  7. java tcc事务 例子_分布式事务之TCC事务模型

    正文 我们先套一个业务场景进去,如下图所示 那页面点了支付按钮,调用支付服务,那我们后台要实现下面三个步骤 [1] 订单服务-修改订单状态 [2] 账户服务-扣减金钱 [3] 库存服务-扣减库存 达到 ...

  8. 分布式事务讲解 - TX-LCN分布式事务框架(含LCN、TCC、TXC三种模式)

    分布式事务讲解 - TX-LCN分布式事务框架(含LCN.TCC.TXC三种模式) 分布式事务系列博客: TX-LCN框架原理 LCN 原理及主要特点 代码实现 实现场景 创建数据库及表(三个数据库, ...

  9. 分布式事务——分布式事务简介、分布式事务框架 Seata(AT模式、Tcc模式、Tcc Vs AT)、分布式事务—MQ

    分布式事务--分布式事务简介.分布式事务框架 Seata(AT模式.Tcc模式.Tcc Vs AT).分布式事务--MQ 一.分布式事务简介 如果不是分布式环境的话一般不会接触到这种,一旦是微服务这种 ...

最新文章

  1. Redis系列(七):缓存只是读写回种这么简单吗?如果是,那么请你一定看看这篇文章!...
  2. AES和TKIP的区别
  3. C# 在DataGridView中,点击单元格调出 TreeView控件 或 ListBox控件
  4. 位操作——整数用位存储
  5. mouseleave mouseout时候悬浮框不应该消失的时候消失了 css 解决办法
  6. python 生成excel像素画_【译】只用 CSS 就能做到的像素画/像素动画
  7. Java编程:栈的应用实例——简单的综合计算器实现(中缀表达式)
  8. python3.5 pip升级_ubuntu下 python3.5 pip3的安装 升级与卸载
  9. 高数18讲_1000题错题总结_第三四讲
  10. Protues仿真 8X8 LED点阵
  11. 手机端APP接口拦截(抓包)-Charles
  12. 新手入门吉他买什么好?十年吉他老司机教你如何远离烧火棍,附上靠谱吉他品牌推荐!
  13. c++学习:多态案例之魔法门英雄无敌
  14. java 扫描tcp端口号_Java 端口扫描器 TCP的实现方法
  15. 2021秋软工实践第二次结对编程作业
  16. 167. 两数之和 II - 输入有序数组(java)
  17. jdk,jre,jvm的包含关系
  18. 联想计算机系统还原怎么弄,【联想笔记本电脑一键还原的使用方法】 联想一键还原...
  19. docker多容器操作与强制删除容器的方法步骤
  20. Android Audio(七)—— AudioFocus(duck)

热门文章

  1. poj2594(最小可相交覆盖路径问题)
  2. 图论 ---- 思维搜索全图任意位置传送 1520G - To Go Or Not To Go?
  3. 广东2021高考成绩查询6,2021全国高考6月10日全部结束 预计最快6月23日可查高考成绩...
  4. ef mysql 事务_事务 - EF Core | Microsoft Docs
  5. mysql5.6.42 力资源_MySQL5.6.42解压版安装教程
  6. 【蓝桥杯】2019年第十届蓝桥杯省赛B组试题J — 灵能传输(前缀和,猜想结论)难度⭐⭐⭐⭐
  7. 【题解】P1029 最大公约数和最小公倍数问题
  8. ubuntu php7 memcache,linux ubuntu下安装php memcache扩展
  9. MySQL第九章索引_MySQL高级(索引优化+慢查询定位)
  10. HTML在表格右边增加一个表格,如何在表格右侧增加一列