当前位置:首页 > 单片机 > 架构师社区
[导读]      一、背景 有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了能够快速迭代上线,往往不太注重产品/项目/系统的高可靠性、高性能与高扩展性,采用单体应用和单实例数据库的架构方式快速迭代开发;当产品/项目/系统做到一定规模的时候,原有的系

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

     

一、背景


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


本文就针对项目拆分后数据库的分布式事务问题,基于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 {  /** * 测试预保存资产(状态为待确认) */ @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接口实现类


/*** @author binghe* 资产相关的业务发布Dubbo服务的实现*/@Service@Componentpublic 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状态为-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项目,该项目提供界面查看事务日志,打包后部署即可,我们这里就不在作详细的描述。


四、TCC执行流程


业务流程使用记录:

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

try:

用户金额冻结

调用积分处理TCC:

try:预增加积分

confirm:更新增加积分状态

cancel:取消增加的积分

confirm:

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

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

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

cancel:

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

用户冻结金额释放


特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

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

长按订阅更多精彩▼

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

如有收获,点个在看,诚挚感谢

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭