分布式事务之——tcc-transaction分布式TCC型事务框架搭建与实战案例(基于Dubbo/Dubbox)

时间:2022-09-03 08:13:21

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73731363

一、背景

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

本文就针对项目拆分后数据库的分布式事务问题,基于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文件
  1. <!-- 第一处:修改版本为4.3.4 -->
  2. <springframework.version>4.3.4.RELEASE</springframework.version>
  3.  
  4. <!-- 第二处:修改版本为2.2.1 -->
  5. <dependency>
  6. <groupId>org.quartz-scheduler</groupId>
  7. <artifactId>quartz</artifactId>
  8. <version>2.2.1</version>
  9. <exclusions>
  10. <exclusion>
  11. <groupId>c3p0</groupId>
  12. <artifactId>c3p0</artifactId>
  13. </exclusion>
  14. </exclusions>
  15. </dependency>
  16.  
  17. <!-- 第三处:修改版本为2.5.3 -->
  18. <dependency>
  19. <groupId>com.alibaba</groupId>
  20. <artifactId>dubbo</artifactId>
  21. <version>2.5.3</version>
  22. </dependency>
1.2 修改 tcc-transaction-spring/src/main/java/org/mengyun/tcctransaction/spring/recover/RecoverScheduledJob.java

该文件中 CronTriggerBean类在4.x中已经不存在,也是修改源码主要修改的地方。
修改其中的init方法,修改后如下:

  1. public void init() {
  2. try {
  3. MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
  4. jobDetail.setTargetObject(transactionRecovery);
  5. jobDetail.setTargetMethod("startRecover");
  6. jobDetail.setName("transactionRecoveryJob");
  7. jobDetail.setConcurrent(false);
  8. jobDetail.afterPropertiesSet();
  9.  
  10. CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
  11. cronTrigger.setBeanName("transactionRecoveryCronTrigger");
  12. cronTrigger.setJobDetail(jobDetail.getObject());
  13.  
  14. cronTrigger.setCronExpression(transactionConfigurator.getRecoverConfig().getCronExpression());
  15. cronTrigger.afterPropertiesSet();
  16.  
  17. scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject());
  18.  
  19. scheduler.start();
  20.  
  21. } catch (Exception e) {
  22. throw new SystemException(e);
  23. }
  24. }

各位也可参考如下的修改: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的配置文件中引入,如下:

  1. <!-- TCC Transaction -->
  2. <import resource="classpath:tcc-transaction.xml" />

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

4.设置TransactionRepository

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

  1. <!--tcc-->
  2. <bean id="tccDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  3. <property name="driverClassName" value="${jdbc.driverClassName}" />
  4. <property name="url" value="${jdbc.tcc.url}" />
  5. <property name="username" value="${jdbc.username}" />
  6. <property name="password" value="${jdbc.password}" />
  7. <property name="initialSize" value="${dbcp.initialSize}" />
  8. <property name="maxActive" value="${dbcp.maxActive}" />
  9. <property name="maxIdle" value="${dbcp.maxIdle}" />
  10. <property name="maxWait" value="${dbcp.maxWait}" />
  11. <property name="poolPreparedStatements" value="${dbcp.poolPreparedStatements}" />
  12. <property name="defaultAutoCommit" value="${dbcp.defaultAutoCommit}" />
  13. <property name="timeBetweenEvictionRunsMillis" value="${dbcp.timeBetweenEvictionRunsMillis}" />
  14. <property name="minEvictableIdleTimeMillis" value="${dbcp.minEvictableIdleTimeMillis}" />
  15. </bean>
  16.  
  17. <bean id="transactionRepository"
  18. class="org.mengyun.tcctransaction.spring.repository.SpringJdbcTransactionRepository">
  19. <property name="dataSource" ref="tccDataSource"/>
  20. <property name="domain" value="SAAS"/>
  21. <property name="tbSuffix" value="_ASSET"/>
  22. </bean>
  23.  
  24. <bean class="org.mengyun.tcctransaction.spring.recover.DefaultRecoverConfig">
  25. <property name="maxRetryCount" value="30"/>
  26. <property name="recoverDuration" value="120"/>
  27. <property name="cronExpression" value="0 */1 * * * ?"/>
  28. <property name="delayCancelExceptions">
  29. <util:set>
  30. <value>com.alibaba.dubbo.remoting.TimeoutException</value>
  31. </util:set>
  32. </property>
  33. </bean>

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

5.mysql建表脚本

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

  1. CREATE TABLE `tcc_transaction_asset` (
  2. `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT,
  3. `DOMAIN` varchar(100) DEFAULT NULL,
  4. `GLOBAL_TX_ID` varbinary(32) NOT NULL,
  5. `BRANCH_QUALIFIER` varbinary(32) NOT NULL,
  6. `CONTENT` varbinary(8000) DEFAULT NULL,
  7. `STATUS` int(11) DEFAULT NULL,
  8. `TRANSACTION_TYPE` int(11) DEFAULT NULL,
  9. `RETRIED_COUNT` int(11) DEFAULT NULL,
  10. `CREATE_TIME` datetime DEFAULT NULL,
  11. `LAST_UPDATE_TIME` datetime DEFAULT NULL,
  12. `VERSION` int(11) DEFAULT NULL,
  13. PRIMARY KEY (`TRANSACTION_ID`),
  14. UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)
  15. ) ENGINE=InnoDB DEFAULT CHARSET=utf8

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

6.发布服务(重点)

6.1 dubbo接口
  1. /**
  2. * @author liuyazhuang
  3. * 资产相关的业务发布Dubbo服务
  4. */
  5. public interface AssetCardService {
  6.  
  7. /**
  8. * 测试预保存资产(状态为待确认)
  9. */
  10. @Compensable
  11. int testSaveAssetCard(AssetCardModel model);
  12.  
  13. /**
  14. * 确认保存资产到mysql(状态为正常)
  15. */
  16. int confirmMysqlSaveAssetCard(AssetCardModel model);
  17.  
  18. /**
  19. * 取消保存资产到msyql(更新状态为删除)
  20. */
  21. int cancelMysqlSaveAssetCard(AssetCardModel model);
  22.  
  23. /**
  24. * 预保存资产到mongo(状态为待确认)
  25. */
  26. @Compensable
  27. void processMongo(AssetCardModel model);
  28.  
  29. /**
  30. * 确认保存资产到mongo(状态为正常)
  31. */
  32. void confirmMongoSaveAssetCard(AssetCardModel model);
  33.  
  34. /**
  35. * 取消保存资产到mongo(更新状态为删除)
  36. */
  37. void cancelMongoSaveAssetCard(AssetCardModel model);
  38.  
  39. }

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

6.2 dubbo接口实现类
  1. /**
  2. * @author liuyazhuang
  3. * 资产相关的业务发布Dubbo服务的实现
  4. */
  5. @Service
  6. @Component
  7. public class AssetCardServiceImpl implements AssetCardService{
  8. @Override
  9. @Compensable(confirmMethod = "confirmMysqlSaveAssetCard", cancelMethod = "cancelMysqlSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)
  10. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
  11. public int testSaveAssetCard(AssetCardModel model){
  12.  
  13. // 保存mysql,data状态为-1
  14. model.setDataStatus(-1);
  15. assetCardDao.insert(model);
  16.  
  17.  
  18. // mongo处理
  19. assetCardService.processMongo(model);
  20.  
  21. return model.getId();
  22. }
  23.  
  24. @Override
  25. public int confirmMysqlSaveAssetCard(AssetCardModel model){
  26. System.out.println("============================================================================");
  27. System.out.println("=================mysql:confirm");
  28. System.out.println("============================================================================");
  29.  
  30. // 更新mysql data_status为0
  31. model.setDataStatus(0);
  32. assetCardDao.updateByPrimaryKey(model);
  33.  
  34. return model.getId();
  35. }
  36.  
  37. @Override
  38. public int cancelMysqlSaveAssetCard(AssetCardModel model){
  39. System.out.println("============================================================================");
  40. System.out.println("=================mysql:cancel");
  41. System.out.println("============================================================================");
  42.  
  43. // 更新mysql data_status为-1
  44. model.setDataStatus(-1);
  45. assetCardDao.updateByPrimaryKey(model);
  46.  
  47. return model.getId();
  48. }
  49.  
  50. @Compensable(confirmMethod = "confirmMongoSaveAssetCard", cancelMethod = "cancelMongoSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class)
  51. @Override
  52. public void processMongo(AssetCardModel model) {
  53.  
  54. // 保存mongo,data_statu为-1
  55. model.setDataStatus(-1);
  56. assetCardDaoWrapper.saveMongo(model);
  57. }
  58.  
  59. @Override
  60. public void confirmMongoSaveAssetCard(AssetCardModel model){
  61. System.out.println("============================================================================");
  62. System.out.println("=================mongo:confirm");
  63. System.out.println("============================================================================");
  64.  
  65. // 更新mongo data_status为0
  66. model.setDataStatus(0);
  67. assetCardDaoWrapper.updateMongo(model);
  68. }
  69.  
  70. @Override
  71. public void cancelMongoSaveAssetCard(AssetCardModel model){
  72. System.out.println("============================================================================");
  73. System.out.println("=================mongo:cancel");
  74. System.out.println("============================================================================");
  75.  
  76. // 更新mongo data_status为-1
  77. model.setDataStatus(-1);
  78. assetCardDao.updateByPrimaryKey(model);
  79. assetCardDaoWrapper.updateMongo(model);
  80. }
  81. }

注意点:
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 调用方
  1. @Override
  2. @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })
  3. public long testSaveAssetCard(AssetCardModel assetCardModel) throws AssetException {
  4. assetCardModel.setId(IdGenerator.getId());
  5. return assetCardService.testSaveAssetCard(assetCardModel);
  6. }

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

7.事务查看

源码中提供tcc-transaction-server web项目,该项目提供界面查看事务日志,打包后部署即可,我们这里就不在作详细的描述。

四、TCC执行流程

业务流程使用记录:
前提:用户下单,建立订单,创建支付记录,支付记录状态为待支付
try:
用户金额冻结
调用积分处理TCC:
try:预增加积分
confirm:更新增加积分状态
cancel:取消增加的积分
confirm:
订单支付状态更新为已支付
订单支付记录支付状态更新为已支付
用户金额扣款(以上三个操作在同一本地事务)
cancel:
判断订单支付状态与订单记录支付状态为未支付
用户冻结金额释放

分布式事务之——tcc-transaction分布式TCC型事务框架搭建与实战案例(基于Dubbo/Dubbox)的更多相关文章

  1. 【转】分布式事务之——tcc-transaction分布式TCC型事务框架搭建与实战案例

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73731363 一.背景 有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了 ...

  2. 如何开发基于Dubbo RPC的分布式服务?

    什么是Dubbo? Dubbo能做什么? 在Crystal框架下,如何开发基于Dubbo RPC的服务? 在Crystal框架下,如何调用Dubbo RPC服务? 相关的文章 什么是Dubbo? Du ...

  3. 分布式事务(四)之TCC

    在电商领域等互联网场景下,传统的事务在数据库性能和处理能力上都暴露出了瓶颈.在分布式领域基于CAP理论以及BASE理论,有人就提出了柔性事务的概念.在业内,关于柔性事务,最主要的有以下四种类型:两阶段 ...

  4. 微服务痛点-基于Dubbo &plus; Seata的分布式事务&lpar;TCC模式&rpar;

    前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...

  5. WCF分布式开发步步为赢&lpar;12&rpar;&colon;WCF事务机制(Transaction)和分布式事务编程

    今天我们继续学习WCF分布式开发步步为赢系列的12节:WCF事务机制(Transaction)和分布式事务编程.众所周知,应用系统开发过程中,事务是一个重要的概念.它是保证数据与服务可靠性的重要机制. ...

  6. 分析 5种分布式事务方案,还是选了阿里的 Seata(原理 &plus; 实战)

    好长时间没发文了,最近着实是有点忙,当爹的第 43 天,身心疲惫.这又赶上年底,公司冲 KPI 强制技术部加班到十点,晚上孩子隔两三个小时一醒,基本没睡囫囵觉的机会,天天处于迷糊的状态,孩子还时不时起 ...

  7. 微服务痛点-基于Dubbo &plus; Seata的分布式事务&lpar;AT&rpar;模式

    前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...

  8. 分布式事务&lpar;3&rpar;---RocketMQ实现分布式事务原理

    分布式事务(3)-RocketMQ实现分布式事务原理 之前讲过有关分布式事务2PC.3PC.TCC的理论知识,博客地址: 1.分布式事务(1)---2PC和3PC原理 2.分布式事务(2)---TCC ...

  9. Redis的Java客户端Jedis的八种调用方式&lpar;事务、管道、分布式&rpar;介绍

    jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...

随机推荐

  1. Windows 上使用 cygwin 连接到 docker toolbox

    Windows 上使用 cygwin 连接到 docker toolbox Docker 确实给软件开发带来一些好处,在简化部署.统一开发.测试和生产环境上,有它独到的理念.Linux 上可直接安装 ...

  2. Mongdb操作嵌套文档

    1.一个文档如下 db.posts.find() { "_id" : ObjectId("5388162dfc164ee1f39be37f"), "t ...

  3. 黑马程序员Java基础班&plus;就业班课程笔记全发布(持续更新)

    正在黑马学习,整理了一些课程知识点和比较重要的内容分享给大家,也是给自己拓宽一些视野,仅供大家交流学习,大家有什么更好的内容可以发给我 ,现有黑马教程2000G  QQ 1481135711 这是我总 ...

  4. Socket编程实践&lpar;10&rpar; --select的限制与poll的使用

    select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或 ...

  5. jenkins自动化工具使用教程

    自动化构建.测试.部署.代码检测越来越重要.主要有一下几点原因 1.  企业做大,项目变多,多端支持(web,h5,小程序等) 2.  微服务提倡高内聚低耦合,项目因拆分变多 3.  DevOps自动 ...

  6. Qt License 解读

    对于桌面和移动平台应用 官方说明如下 Qt for Application Development lets you create applications for desktop and mobil ...

  7. fedora23没有&sol;var&sol;log&sol;messages &amp&semi;如何禁用后台自动更新软件&quest;

    警告!! Linux是一个非常敏感的操作系统,若删除文件错误,很容易造成系统崩溃. fedora23没有/var/log/messages 不是没有messages这个文件,而是 从 fc core ...

  8. mysql中用limit 进行分页有两种方式

    代码示例:语句1: select * from student limit 9,4 语句2: slect * from student limit 4 offset 9 // 语句1和2均返回表stu ...

  9. AndroidStudio相关经验记录

    1.初次打开Gradle工程特别慢,一直提示下载更新Gradle 解决办法:打开Gradle工程子目录:“\gradle\wrapper” 下的 “gradle-wrapper.properties” ...

  10. Tunnel Warfare(hdu1540 线段树)

    Tunnel Warfare Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) T ...