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

2022-08-27 13:14:24 浏览数 (1)

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

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

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

本文demo代码:GitHub

依赖

代码语言:javascript复制
        <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如下:

代码语言:javascript复制
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自带, 这样 try, confirm, cancel 调用会落在同一个节点 充分利用了缓存,提搞了效率。在你的yaml配置如下:
代码语言:javascript复制
hmily:
  ribbon:
    rule:
      enabled: true
  • 如果用户配置了feign.hystrix.enabled = true, 默认使用线程池模式, 将会开启 HmilyHystrixConcurrencyStrategy 它在hystrix使用线程池模式的时候,能够照样通过threadLoacl 进行RPC传参数。
  • 需要进行分布式事务的SpringCloud微服务的调用方需要设置不重试:
代码语言:javascript复制
#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侧服务)中:

代码语言:javascript复制
    //全局事务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侧服务)中:

代码语言:javascript复制
    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调用传播事务
  • try, confirm, cancel 方法的所有异常不要自行catch 任何异常都应该抛出给 Hmily框架处理
  • 消费者feign请求的本地接口如果存在fallback方法的话,要么保证fallback也会出现异常,要么不要fallback,否则异常无法被Hmily捕获

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

0 人点赞