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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:https://github.com/sunshinelyz/mykit-delayPS: 欢迎各位Star源码,也可以pr你牛逼哄哄的代码。

一、背景


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


本文就针对项目拆分后数据库的分布式事务问题,基于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
@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:

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

用户冻结金额释放

后记:

记住:你比别人强的地方,不是你做过多少年的CRUD工作,而是你比别人掌握了更多深入的技能。不要总停留在CRUD的表面工作,理解并掌握底层原理并熟悉源码实现,并形成自己的抽象思维能力,做到灵活运用,才是你突破瓶颈,脱颖而出的重要方向!

你在刷抖音,玩游戏的时候,别人都在这里学习,成长,提升,人与人最大的差距其实就是思维。你可能不信,优秀的人,总是在一起。

扫一扫或长按二维码

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
8月前
|
数据采集 存储 数据可视化
分布式爬虫框架Scrapy-Redis实战指南
本文介绍如何使用Scrapy-Redis构建分布式爬虫系统,采集携程平台上热门城市的酒店价格与评价信息。通过代理IP、Cookie和User-Agent设置规避反爬策略,实现高效数据抓取。结合价格动态趋势分析,助力酒店业优化市场策略、提升服务质量。技术架构涵盖Scrapy-Redis核心调度、代理中间件及数据解析存储,提供完整的技术路线图与代码示例。
796 0
分布式爬虫框架Scrapy-Redis实战指南
|
6月前
|
监控 Java 调度
SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战
本文对比分析了SpringBoot中的`@Scheduled`与Quartz定时任务框架。`@Scheduled`轻量易用,适合单机简单场景,但存在多实例重复执行、无持久化等缺陷;Quartz功能强大,支持分布式调度、任务持久化、动态调整和失败重试,适用于复杂企业级需求。文章通过特性对比、代码示例及常见问题解答,帮助开发者理解两者差异,合理选择方案。记住口诀:单机简单用注解,多节点上Quartz;若是任务要可靠,持久化配置不能少。
594 4
|
10月前
|
数据采集 人工智能 分布式计算
MaxFrame:链接大数据与AI的高效分布式计算框架深度评测与实践!
阿里云推出的MaxFrame是链接大数据与AI的分布式Python计算框架,提供类似Pandas的操作接口和分布式处理能力。本文从部署、功能验证到实际场景全面评测MaxFrame,涵盖分布式Pandas操作、大语言模型数据预处理及企业级应用。结果显示,MaxFrame在处理大规模数据时性能显著提升,代码兼容性强,适合从数据清洗到训练数据生成的全链路场景...
487 5
MaxFrame:链接大数据与AI的高效分布式计算框架深度评测与实践!
|
10月前
|
消息中间件 算法 调度
分布式系统学习10:分布式事务
本文是小卷关于分布式系统架构学习系列的第13篇,重点探讨了分布式事务的相关知识。随着业务增长,单体架构拆分为微服务后,传统的本地事务无法满足需求,因此需要引入分布式事务来保证数据一致性。文中详细介绍了分布式事务的必要性、实现方案及其优缺点,包括刚性事务(如2PC、3PC)和柔性事务(如TCC、Saga、本地消息表、MQ事务、最大努力通知)。同时,还介绍了Seata框架作为开源的分布式事务解决方案,提供了多种事务模式,简化了分布式事务的实现。
442 5
|
10月前
|
人工智能 分布式计算 大数据
MaxFrame 产品评测:大数据与AI融合的Python分布式计算框架
MaxFrame是阿里云MaxCompute推出的自研Python分布式计算框架,支持大规模数据处理与AI应用。它提供类似Pandas的API,简化开发流程,并兼容多种机器学习库,加速模型训练前的数据准备。MaxFrame融合大数据和AI,提升效率、促进协作、增强创新能力。尽管初次配置稍显复杂,但其强大的功能集、性能优化及开放性使其成为现代企业与研究机构的理想选择。未来有望进一步简化使用门槛并加强社区建设。
458 8
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
246 2
|
Dubbo 应用服务中间件 Apache
Star 4w+,Apache Dubbo 3.3 全新发布,Triple X 领衔,开启微服务通信新时代
在 Apache Dubbo 突破 4w Star 之际,Apache Dubbo 团队正式宣布,Dubbo 3.3 正式发布!作为全球领先的开源微服务框架,Dubbo 一直致力于为开发者提供高性能、可扩展且灵活的分布式服务解决方案。此次发布的 Dubbo 3.3,通过 Triple X 的全新升级,突破了以往局限,实现了对南北向与东西向流量的全面支持,并提升了对云原生架构的友好性。
349 100
|
Dubbo Java 应用服务中间件
💥Spring Cloud Dubbo火爆来袭!微服务通信的终极利器,你知道它有多强大吗?🔥
【8月更文挑战第29天】随着信息技术的发展,微服务架构成为企业应用开发的主流模式,而高效的微服务通信至关重要。Spring Cloud Dubbo通过整合Dubbo与Spring Cloud的优势,提供高性能RPC通信及丰富的生态支持,包括服务注册与发现、负载均衡和容错机制等,简化了服务调用管理并支持多种通信协议,提升了系统的可伸缩性和稳定性,成为微服务通信领域的优选方案。开发者仅需关注业务逻辑,而无需过多关心底层通信细节,使得Spring Cloud Dubbo在未来微服务开发中将更加受到青睐。
199 0

热门文章

最新文章