转载请注明出处: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文件
- <!-- 第一处:修改版本为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 打包并发布
这里我们通过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 liuyazhuang
- * 资产相关的业务发布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 liuyazhuang
- * 资产相关的业务发布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状态为-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型事务框架搭建与实战案例
转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73731363 一.背景 有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了 ...
-
如何开发基于Dubbo RPC的分布式服务?
什么是Dubbo? Dubbo能做什么? 在Crystal框架下,如何开发基于Dubbo RPC的服务? 在Crystal框架下,如何调用Dubbo RPC服务? 相关的文章 什么是Dubbo? Du ...
-
分布式事务(四)之TCC
在电商领域等互联网场景下,传统的事务在数据库性能和处理能力上都暴露出了瓶颈.在分布式领域基于CAP理论以及BASE理论,有人就提出了柔性事务的概念.在业内,关于柔性事务,最主要的有以下四种类型:两阶段 ...
-
微服务痛点-基于Dubbo + Seata的分布式事务(TCC模式)
前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...
-
WCF分布式开发步步为赢(12):WCF事务机制(Transaction)和分布式事务编程
今天我们继续学习WCF分布式开发步步为赢系列的12节:WCF事务机制(Transaction)和分布式事务编程.众所周知,应用系统开发过程中,事务是一个重要的概念.它是保证数据与服务可靠性的重要机制. ...
-
分析 5种分布式事务方案,还是选了阿里的 Seata(原理 + 实战)
好长时间没发文了,最近着实是有点忙,当爹的第 43 天,身心疲惫.这又赶上年底,公司冲 KPI 强制技术部加班到十点,晚上孩子隔两三个小时一醒,基本没睡囫囵觉的机会,天天处于迷糊的状态,孩子还时不时起 ...
-
微服务痛点-基于Dubbo + Seata的分布式事务(AT)模式
前言 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata 将为用户提供了 AT.TCC.SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案. ...
-
分布式事务(3)---RocketMQ实现分布式事务原理
分布式事务(3)-RocketMQ实现分布式事务原理 之前讲过有关分布式事务2PC.3PC.TCC的理论知识,博客地址: 1.分布式事务(1)---2PC和3PC原理 2.分布式事务(2)---TCC ...
-
Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍
jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...
随机推荐
-
Windows 上使用 cygwin 连接到 docker toolbox
Windows 上使用 cygwin 连接到 docker toolbox Docker 确实给软件开发带来一些好处,在简化部署.统一开发.测试和生产环境上,有它独到的理念.Linux 上可直接安装 ...
-
Mongdb操作嵌套文档
1.一个文档如下 db.posts.find() { "_id" : ObjectId("5388162dfc164ee1f39be37f"), "t ...
-
黑马程序员Java基础班+就业班课程笔记全发布(持续更新)
正在黑马学习,整理了一些课程知识点和比较重要的内容分享给大家,也是给自己拓宽一些视野,仅供大家交流学习,大家有什么更好的内容可以发给我 ,现有黑马教程2000G QQ 1481135711 这是我总 ...
-
Socket编程实践(10) --select的限制与poll的使用
select的限制 用select实现的并发服务器,能达到的并发数一般受两方面限制: 1)一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n(number)来调整或 ...
-
jenkins自动化工具使用教程
自动化构建.测试.部署.代码检测越来越重要.主要有一下几点原因 1. 企业做大,项目变多,多端支持(web,h5,小程序等) 2. 微服务提倡高内聚低耦合,项目因拆分变多 3. DevOps自动 ...
-
Qt License 解读
对于桌面和移动平台应用 官方说明如下 Qt for Application Development lets you create applications for desktop and mobil ...
-
fedora23没有/var/log/messages &;如何禁用后台自动更新软件?
警告!! Linux是一个非常敏感的操作系统,若删除文件错误,很容易造成系统崩溃. fedora23没有/var/log/messages 不是没有messages这个文件,而是 从 fc core ...
-
mysql中用limit 进行分页有两种方式
代码示例:语句1: select * from student limit 9,4 语句2: slect * from student limit 4 offset 9 // 语句1和2均返回表stu ...
-
AndroidStudio相关经验记录
1.初次打开Gradle工程特别慢,一直提示下载更新Gradle 解决办法:打开Gradle工程子目录:“\gradle\wrapper” 下的 “gradle-wrapper.properties” ...
-
Tunnel Warfare(hdu1540 线段树)
Tunnel Warfare Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) T ...