分布式事务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捕获

 

 

 

 

 

 

 

 

 

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • ucinet网络分析实例(网络分析app)

    ucinet介绍UCINET为菜单驱动的Windows程序,可能是最知名和最经常被使用的处理社会网络数据和其他相似性数据的综合性分析程序。与UCINET捆绑在一起的还有Pajek、Mage和NetDraw等三个软件。UCINET能够处理的原始数据为矩阵格式,提供了大量数据管理和转化工具。该程序本身不包含网络可视化的图形程序,但可将数据和处理结果输出至NetDraw、Pajek、Mage和Kr…

  • a*算法最短路径_最长路径算法

    a*算法最短路径_最长路径算法#include#include#include#include#include#defineN1000#defineinf1<<30;usingnamespacestd;/* a星算法,找寻最短路径 算法核心:有两个表open表和close表 将方块添加到open列表中,该列表有最小的和值。且将这个方块称为S吧。 将S从open列表移除,然后添加

  • 豪华版飞机大战系列(六)–附源代码

    豪华版飞机大战系列(六)–附源代码

  • CodeBlocks 中文乱码解决方法「建议收藏」

    CodeBlocks 中文乱码解决方法「建议收藏」Windows下,按照安装步骤一步步来就行,由于之前不知道怎么设置错误,然后就出现中文乱码问题,出现找了很多方法,但都不合适,最后自己一点点摸索,无非就是尽量需找默认设置,步骤如下:(1)按照下图去选择(2)settings->globalcompilersettings点击一下resetdefaults,确定,就可以了!

  • Java NIO与IO的区别

    Java NIO与IO的区别Java.nio 俗称 New IO (从1.4开始),全称是Java Non-blocking IO,即非阻塞的IO,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络IO。

  • Sping 源码深度解析——容器的功能扩展 【学习笔记】

    我为什么 看的不够远,因为高度不够!学习和整理总结Spring容器的功能扩展,本文为学习笔记,其中有一些内容是自己的思考总结!一、两种Spring中bean加载的方式第一种# 第一种使用 BeanFactory 以及它默认的实现类 XmlBeanFactoryBeanFactory bf = new XmlBeanFactory(new ClassPathReso…

发表回复

您的电子邮箱地址不会被公开。

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