分布式事务TCC方案Hmily——springcloud + feign + mybatis

分布式事务TCC方案Hmily——springcloud + feign + mybatisTCC理论:分布式事务基础理论——TCCHmily介绍:分布式事务TCC方案——Hmily金融级柔性分布式事务解决方案介绍本文demo代码:GitHub依赖<dependency><groupId>org.dromara</groupId><artifactId>hmily-springcloud</artifactId><vers

大家好,又见面了,我是你们的朋友全栈君。

TCC理论:分布式事务基础理论——TCC

Hmily介绍:分布式事务TCC方案——Hmily金融级柔性分布式事务解决方案介绍

本文demo代码:GitHub

依赖

        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-springcloud</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-spring-boot-starter-springcloud</artifactId>
            <version>2.1.1</version>
        </dependency>

配置

配置具体请根据自身需要参考:官方配置详解

本文实例以本地配置文件模式,hmily.yml如下:

hmily:
  server:
    configMode: local
    appName: account1
  #  如果server.configMode eq local 的时候才会读取到这里的配置信息.
  config:
    appName: account1
    serializer: kryo
    contextTransmittalMode: threadLocal
    scheduledThreadMax: 16
    scheduledRecoveryDelay: 60
    scheduledCleanDelay: 60
    scheduledPhyDeletedDelay: 600
    scheduledInitDelay: 30
    recoverDelayTime: 60
    cleanDelayTime: 180
    limit: 200
    retryMax: 10
    bufferSize: 8192
    consumerThreads: 16
    asyncRepository: true
    autoSql: true
    phyDeleted: true
    storeDays: 3
    repository: mysql

repository:
  database:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/hmily?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: lyl512240816
    maxActive: 20
    minIdle: 10
    connectionTimeout: 30000
    idleTimeout: 600000
    maxLifetime: 1800000
  file:
    path:
    prefix: /hmily
  mongo:
    databaseName:
    url:
    userName:
    password:
  zookeeper:
    host: localhost:2181
    sessionTimeOut: 1000
    rootPath: /hmily
  redis:
    cluster: false
    sentinel: false
    clusterUrl:
    sentinelUrl:
    masterName:
    hostName:
    port:
    password:
    maxTotal: 8
    maxIdle: 8
    minIdle: 2
    maxWaitMillis: -1
    minEvictableIdleTimeMillis: 1800000
    softMinEvictableIdleTimeMillis: 1800000
    numTestsPerEvictionRun: 3
    testOnCreate: false
    testOnBorrow: false
    testOnReturn: false
    testWhileIdle: false
    timeBetweenEvictionRunsMillis: -1
    blockWhenExhausted: true
    timeOut: 1000

metrics:
  metricsName: prometheus
  host:
  port: 9091
  async: true
  threadCount : 16
  jmxConfig:

spring配置说一下与hmily有关的配置:

  • 如果服务部署了几个节点, 负载均衡算法最好使用 hmily自带, 这样 tryconfirmcancel 调用会落在同一个节点 充分利用了缓存,提搞了效率。在你的yaml配置如下:
hmily:
  ribbon:
    rule:
      enabled: true
  • 如果用户配置了feign.hystrix.enabled = true, 默认使用线程池模式, 将会开启 HmilyHystrixConcurrencyStrategy 它在hystrix使用线程池模式的时候,能够照样通过threadLoacl 进行RPC传参数。
  • 需要进行分布式事务的SpringCloud微服务的调用方需要设置不重试:
#Ribbon的负载均衡策略
ribbon:
  NFLoadBalancerRuleClassName:  com.netflix.loadbalancer.RandomRule
  ConnectTimeout: 600 # 设置连接超时时间 default 2000
  ReadTimeout: 6000    # 设置读取超时时间  default 5000
  OkToRetryOnAllOperations: fasle # 对所有操作请求都进行重试  default false
  MaxAutoRetriesNextServer: 0    # 切换实例的重试次数  default 1
  MaxAutoRetries: 0     # 对当前实例的重试次数 default 0

业务代码

本文demo示例是模拟一个银行转账的场景,用户a转账给用户b,转账过程通过feign远程调用。

  1. 调用account-server1接口,传入转账金额,扣除用户a账户
  2. account-server1远程调用account-server2,给用户b账户加钱
  3. 自行解决空回滚、悬挂、幂等性问题(目前Hmily是已经完成了对空回滚、悬挂的自动处理,无需开发者自行处理,代码中已经自行处理,请忽略该部分处理的代码)

 

主要代码

转账服务account-server1(用户a侧服务)中:

    //全局事务id
    private String transId;

    // 账户扣款,就是tcc的try方法

    /**
     * 	try幂等校验
     * 	try悬挂处理
     * 	检查余额是够扣减金额
     * 	扣减金额
     * @param accountNo
     * @param amount
     */
    @Override
    @Transactional
    //只要标记@Hmily就是try方法,在注解中指定confirm、cancel两个方法的名字
    @HmilyTCC(confirmMethod="commit",cancelMethod="rollback")
    public void updateAccountBalance(String accountNo, Double amount) {
        //获取全局事务id
        transId = String.valueOf(HmilyContextHolder.get().getTransId());
        log.info("bank1 try begin 开始执行...xid:{}",transId);
        //幂等判断 判断local_try_log表中是否有try日志记录,如果有则不再执行
        if(accountMapper.isExistTry(transId)>0){
            log.info("bank1 try 已经执行,无需重复执行,xid:{}",transId);
            return ;
        }

        //try悬挂处理,如果cancel、confirm有一个已经执行了,try不再执行
        if(accountMapper.isExistConfirm(transId)>0 || accountMapper.isExistCancel(transId)>0){
            log.info("bank1 try悬挂处理  cancel或confirm已经执行,不允许执行try,xid:{}",transId);
            return ;
        }

        //扣减金额
        if(accountMapper.subtractAccountBalance(accountNo, amount)<=0){
            //扣减失败
            throw new RuntimeException("bank1 try 扣减金额失败,xid:{}"+transId);
        }
        //插入try执行记录,用于幂等判断
        accountMapper.addTry(transId);

        //远程调用李四,转账
        if(!account2Client.transfer(amount)){
            throw new RuntimeException("bank1 远程调用李四微服务失败,xid:{}"+transId);
        }
        if(amount == 2){
            throw new RuntimeException("人为制造异常,xid:{}"+transId);
        }
        log.info("bank1 try end 结束执行...xid:{}",transId);
    }

    //confirm方法
    @Transactional
    public void commit(String accountNo, Double amount){
        //获取全局事务id
//        String transId = String.valueOf(HmilyContextHolder.get().getTransId());
        log.info("bank1 confirm begin 开始执行...xid:{},accountNo:{},amount:{}",transId,accountNo,amount);
    }



    /** cancel方法
     * 	cancel幂等校验
     * 	cancel空回滚处理
     * 	增加可用余额
     * @param accountNo
     * @param amount
     */
    @Transactional
    public void rollback(String accountNo, Double amount){
        //获取全局事务id
//        String transId = String.valueOf(HmilyContextHolder.get().getTransId());
        log.info("bank1 cancel begin 开始执行...xid:{}",transId);
        //	cancel幂等校验
        if(accountMapper.isExistCancel(transId)>0){
            log.info("bank1 cancel 已经执行,无需重复执行,xid:{}",transId);
            return ;
        }
        //cancel空回滚处理,如果try没有执行,cancel不允许执行
        if(accountMapper.isExistTry(transId)<=0){
            log.info("bank1 空回滚处理,try没有执行,不允许cancel执行,xid:{}",transId);
            return ;
        }
        //	增加可用余额
        accountMapper.addAccountBalance(accountNo,amount);
        //插入一条cancel的执行记录
        accountMapper.addCancel(transId);
        log.info("bank1 cancel end 结束执行...xid:{}",transId);

    }

转账服务account-server2(用户b侧服务)中:

    private String transId;

    @Override
    @HmilyTCC(confirmMethod="confirmMethod", cancelMethod="cancelMethod")
    public void updateAccountBalance(String accountNo, Double amount) {
        //获取全局事务id
        transId = String.valueOf(HmilyContextHolder.get().getTransId());
        log.info("bank2 try begin 开始执行...xid:{}",transId);
    }

    /**
     * confirm方法
     * 	confirm幂等校验
     * 	正式增加金额
     * @param accountNo
     * @param amount
     */
    @Transactional
    public void confirmMethod(String accountNo, Double amount){
        //获取全局事务id
//        String transId = String.valueOf(HmilyContextHolder.get().getTransId());
        log.info("bank2 confirm begin 开始执行...xid:{}",transId);
        if(accountMapper.isExistConfirm(transId)>0){
            log.info("bank2 confirm 已经执行,无需重复执行...xid:{}",transId);
            return ;
        }

        //模拟运行时异常,触发cancel操作回滚
//        Integer.valueOf("QWQEQW");

        //增加金额
        accountMapper.addAccountBalance(accountNo,amount);
        //增加一条confirm日志,用于幂等
        accountMapper.addConfirm(transId);
        log.info("bank2 confirm end 结束执行...xid:{}",transId);
    }



    /**
     * @param accountNo
     * @param amount
     */
    public void cancelMethod(String accountNo, Double amount){
        //获取全局事务id
//        String transId = String.valueOf(HmilyContextHolder.get().getTransId());
        log.info("bank2 cancel begin 开始执行...xid:{}",transId);

    }

需要注意的是:

  • Hmily的全局事务开启于@HmilyTCC(confirmMethod = “confirm”, cancelMethod = “cancel”)注解的方法,confirmMethod指向confirm操作对应的方法,cancelMethod指向cancel操作对应的方法
  • 在服务被调用方的@FeignClient 接口方法上加上 @Hmily注解,就可以通过RPC调用传播事务
  • tryconfirmcancel 方法的所有异常不要自行catch 任何异常都应该抛出给 Hmily框架处理
  • 消费者feign请求的本地接口如果存在fallback方法的话,要么保证fallback也会出现异常,要么不要fallback,否则异常无法被Hmily捕获

 

 

 

 

 

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/145851.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Django(49)drf解析模块源码分析[通俗易懂]

    Django(49)drf解析模块源码分析[通俗易懂]前言上一篇分析了请求模块的源码,如下:definitialize_request(self,request,*args,**kwargs):"""Retu

    2022年8月7日
    2
  • 大数据开发工作累吗?

    大数据开发工作累吗?在大数据时代的背景下,出现了另一类程序员—大数据开发工程师,他们因为掌握着前沿的大数据相关技术,深受企业重视,找工作容易收入也非常高,但作为IT行业的又一分类,是否加班牵动了无数转行者的心!对于大数据开发工作是否加班的这个问题,不能一概而论,需根据公司的实际情况而定,有些公司有加班的氛围,即使非IT岗,也会有加班的情况,这样的公司大数据开发肯定会加班无疑的;有些公司不鼓励加班,会根据大数据开发…

    2022年6月2日
    180
  • 新人如何入行3D游戏建模

    新人如何入行3D游戏建模所有行业都是一样的,没有什么容易的,只不过这一行是偏向于技术的,一个有好的建模师月薪10k+是很常见的,这个需要有自己刻苦学习的成果。游戏建模前景在游戏模型行业,你基本不用担心找不到工作,因为游戏模型师人才缺口非常大。举个例子:游戏制作公司的人员配比大多数是这样的:比如100人的三维制作组,可能有60人在做模型贴图,10个人在K动画。只要你保证技能在手,一定是抢手的人才。在几年前游戏建模这个行业不仅仅缺人才,甚至连新手都非常稀缺,那个时候公司愿意招聘实习生,培养他们然后给公司干活,但是工资一定不会给开的很

    2022年5月12日
    47
  • loadrunner12使用手册_loadrunner脚本编写教程

    loadrunner12使用手册_loadrunner脚本编写教程首先我们安装好loadrunner12之后,出现这三个图标,然后开始操作。操作:1.打开VirtualUserGenerator,新建脚本2.选择脚本协议,对脚本名称位置可以进行编辑。此处以web-http/html为例。3.点击创建后,选择录制脚本,填写好录制的地址,应用程序,然后选择开始录制,此处以测试登录为例4.结束录制后,点击关联回放,确保脚本无误5.对代码进行进一步完善,插入事务或集合点6.对脚本进行执行回放,确保脚本无误,对脚本进行参数化可以直

    2022年10月14日
    0
  • 使用云祺虚拟机备份软件瞬时恢复SANFOR HCI虚拟机

    使用云祺虚拟机备份软件瞬时恢复SANFOR HCI虚拟机使用云祺虚拟机备份软件瞬时恢复SANFOR HCI虚拟机

    2022年4月21日
    41
  • Linux下如何挂载磁盘[通俗易懂]

    Linux下如何挂载磁盘[通俗易懂]使用虚拟机时发现磁盘空间不够了,需要挂载一个磁盘以供继续使用,但是磁盘不是添加就可以使用的,还需要进行挂载。一、添加磁盘添加加新硬盘重启服务器添加完之后就可以重启机器了,如果你机器是开启的,进入系统并不能看见你刚添加的那块磁盘,只有等系统重启,重新加载之后才会显示安装的那块磁盘二、进入系统使用root用户进入系统三、查看硬盘信息[root@localhost~]#fdi

    2022年6月19日
    35

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号