Hmily实现TCC分布式事务

2023-12-20 20:02:42

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
Hmily实现TCC分布式事务


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

前言

提示:这里可以添加本文要记录的大概内容:
TCC(Try-Confirm-Cancel)分布式事务是一种常见的解决方案,它通过三个阶段来保证数据的一致性。Hmily 是一个高性能、易使用、零侵入的分布式事务框架,它提供了强大的事务管理能力。
在本博客中,我们将深入探讨 Hmily 实现 TCC 分布式事务的实战。我将分享我在项目中使用 Hmily 的经验,包括如何配置 Hmily、如何使用 TCC 事务以及如何处理异常情况。


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是Hmily?

定义

Hmily是一个高性能分布式事务TCC开源框架,基于Java语言开发,用于支持Dubbo、Spring Cloud等RPC框架进行分布式事务。它的作用类似于“一手交钱,一手交货”的事务例子,也就是说,在执行分布式事务的过程中,要么全部成功,要么全部失败。Hmily实现的TCC服务与普通的服务一样,只需要暴露一个接口,即它的Try业务,而 Confirm/Cancel业务逻辑,只是为了满足全局事务提交/回滚的需求而提供的,因此不需要被调用它的其他业务服务所感知。

功能

  • 高可靠性:支持分布式场景下,事务异常回滚,超时异常恢复,防止事务悬挂。
  • 易用性:提供零侵入式的Spring-Boot、Spring-Namespace快速与业务系统集成。
  • 高性能:去中心化设计,与业务系统完全融合,天然支持集群部署。
  • 可观测性:Metrics多项指标性能监控,以及admin管理后台UI展示。
  • 多种RPC:支持 Dubbo、SpringCloud、Motan、Sofa-rpc、brpc、tars等知名RPC框架。
  • 日志存储:支持mysql、oracle、mongodb、redis、zookeeper等方式。
  • 复杂场景:支持RPC嵌套调用事务。

使用前提

  • 必须使用 JDK8+
  • TCC模式必须要使用一款 RPC 框架, 比如 : Dubbo, SpringCloud,Montan

二、Hmily实现TCC分布式事务步骤

1.数据库准备

1.在服务器的MySQL命令行执行如下命令创建转出银行数据库和数据表。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for cancel_log
-- ----------------------------
DROP TABLE IF EXISTS `cancel_log`;
CREATE TABLE `cancel_log` (
 `tx_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '全局事务编号',
 `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Cancel阶段执行的日志记录' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of cancel_log
-- ----------------------------
-- ----------------------------
-- Table structure for confirm_log
-- ----------------------------
DROP TABLE IF EXISTS `confirm_log`;
CREATE TABLE `confirm_log` (
 `tx_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '全局事务编号',
 `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Confirm阶段执行的日志记录' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of confirm_log
-- ----------------------------
-- ----------------------------
-- Table structure for try_log
-- ----------------------------
DROP TABLE IF EXISTS `try_log`;
CREATE TABLE `try_log` (
 `tx_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '全局事务编号',
 `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
 PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Try阶段执行的日志记录' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of try_log
-- ----------------------------


-- ----------------------------
-- Table structure for user_account
-- ----------------------------
DROP TABLE IF EXISTS `user_account`;
CREATE TABLE `user_account` (
 `account_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账户编号',
 `account_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账户名称',
 `account_balance` decimal(10, 2) NULL DEFAULT NULL COMMENT '账户余额',
 `transfer_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '转账金额',
 PRIMARY KEY (`account_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '账户信息' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of user_account
-- ----------------------------
INSERT INTO `user_account` VALUES ('1001', '张三', 10000.00, 0.00);


SET FOREIGN_KEY_CHECKS = 1;

2.在服务器的MySQL命令行执行如下命令创建转入银行数据库和数据表。





SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;


-- ----------------------------
-- Table structure for cancel_log
-- ----------------------------
DROP TABLE IF EXISTS `cancel_log`;
CREATE TABLE `cancel_log` (
 `tx_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '全局事务编号',
 `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Cancel阶段执行的日志记录' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of cancel_log
-- ----------------------------


-- ----------------------------
-- Table structure for confirm_log
-- ----------------------------
DROP TABLE IF EXISTS `confirm_log`;
CREATE TABLE `confirm_log` (
 `tx_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '全局事务编号',
 `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Confirm阶段执行的日志记录' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of confirm_log
-- ----------------------------


-- ----------------------------
-- Table structure for try_log
-- ----------------------------
DROP TABLE IF EXISTS `try_log`;
CREATE TABLE `try_log` (
 `tx_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '全局事务编号',
 `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY (`tx_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Try阶段执行的日志记录' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of try_log
-- ----------------------------


-- ----------------------------
-- Table structure for user_account
-- ----------------------------
DROP TABLE IF EXISTS `user_account`;
CREATE TABLE `user_account` (
 `account_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账户编号',
 `account_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账户名称',
 `account_balance` decimal(10, 2) NULL DEFAULT NULL COMMENT '账户余额',
 `transfer_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '转账金额',
 PRIMARY KEY (`account_no`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '账户信息' ROW_FORMAT = Dynamic;


-- ----------------------------
-- Records of user_account
-- ----------------------------
INSERT INTO `user_account` VALUES ('1002', '李四', 10000.00, 0.00);


SET FOREIGN_KEY_CHECKS = 1;

2.项目准备

1.创建maven父工程,并设计为逻辑工程
2.创建maven公共模块
3.创建maven转出银行微服务
4.创建maven传入银行微服务
5.公共模块引入依赖

 <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
  </dependencies>

6.持久层的实现,项目公共模块的持久层是转出银行微服务和转入银行微服务共用的,在逻辑上既实现了转出金额的处理,又实现了转入金额的处理,同时还实现了TCC分布式事务每个阶段执行记录的保存和查询操作。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserAccountDto implements Serializable {


  private static final long serialVersionUID = 3361105512695088121L;


  /**
   * 自定义事务编号
   */
  private String txNo;


  /**
   * 转出账户
   */
  private String sourceAccountNo;


  /**
   * 转入账户
   */
  private String targetAccountNo;
  /**
   * 金额
   */
  private BigDecimal amount;
}

7.Dubbo接口的定义,在整个项目的实现过程中,转出银行微服务和转入银行微服务之间是通过Dubbo实现远程接口调用。因为项目中定义的Dubbo接口需要被转出银行微服务和转入银行微服务同时引用,所以需要将Dubbo接口放在项目的公共模块。

public interface UserAccountBank02Service {
  /**
   * 转账
   */
  void transferAmountToBank2(UserAccountDto userAccountDto);
}

8.转入银行微服务对外提供了转入账户的Dubbo接口,当转出银行微服务调用转入银行微服务的Dubbo接口时,转入银行微服务会执行增加账户余额的操作。

 <!-- dubbo依赖 -->
    <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo-spring-boot-starter</artifactId>
      <version>2.7.15</version>
    </dependency>
    <!--ZooKeeper客户端-->
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>5.2.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>5.2.1</version>
    </dependency>

9.转入银行微服务编写application.yml

server:
  port: 6005
spring:
  datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql://ip:3306/tx-tcc-bank02?useUnicode=true&characterEncoding=utf8
   username: root
   password: 123456


dubbo:
  scan:
   base-packages: com.itbaizhan.service
  application:
   name: tx-tcc-bank02
  registry:
   address: zookeeper://localhost:2181
  protocol:
   name: dubbo
   port: 12345



10.转出银行微服务编写application.yml

server:
  port: 6004
spring:
  datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql://ip:3306/tx-tcc-bank02?useUnicode=true&characterEncoding=utf8
   username: root
   password: 123456


dubbo:
  scan:
   base-packages: com.itbaizhan.service
  application:
   name: tx-tcc-bank01
  registry:
   address: zookeeper://localhost:2181

11.编写转入微服务主启动类

@MapperScan("com.itbaizhan.mapper")
@SpringBootApplication
@Slf4j
public class Bank2Main6005 {
  public static void main(String[] args) {
    SpringApplication.run(Bank2Main6005.class,args);
    log.info("************ Bank2Main6005 启动成功 **********");
   }
}

12.编写转出微服务主启动类

@MapperScan("com.itbaizhan.mapper")
@SpringBootApplication
@Slf4j
public class Bank1Main6004 {
  public static void main(String[] args) {
    SpringApplication.run(Bank1Main6004.class,args);
    log.info("***********  Bank1Main6004 *******");
   }
}

13.转出银行微服务的业务逻辑层主要是实现本地账户的金额扣减操作,通过Dubbo框架实现转入银行微服务对应账户余额的增加操作。

@DubboService(version = "1.0.0")
public class UserAccountServicelmpl implements UserAccountBank02Service {


  @Autowired
  private UserAccountMapper userAccountMapper;


  @Override
  public void transferAmountToBank02(UserAccountDTO userAccountDTO) {
    // 1. 根据账户编号查询账户信息
    UserAccount userAccount = userAccountMapper.selectById(userAccountDTO);
    // 2. 判断账户是否存在
    if (userAccount != null ){
      userAccount.setAccountBalance(userAccount.getAccountBalance().add(userAccountDTO.getBigDecimal()));
      // 3. 更新账户
      userAccountMapper.updateById(userAccount);
     }
   }
}

14.转出微服务实现转账功能,编写转账接口

 /**
   * 跨库转账
   * @param userAccountDTO
   */
  void transferAmountToBank02(UserAccountDTO userAccountDTO);



15.转出微服务转账接口实现

@Service
public class UserAccountServiceImpl implements IUserAccountService {


  @DubboReference(version = "1.0.0")
  private UserAccountBank02Service userAccountBank02Service;




  @Autowired
   private UserAccountMapper userAccountMapper;


  @Override
  public void transferAmountToBank02(UserAccountDTO userAccountDTO) {
    // 1. 根据账户编号查询账户信息
    UserAccount userAccount = userAccountMapper.selectById(userAccountDTO);
    // 2. 判断账户是否存在
    if (userAccount != null && userAccount.getAccountBalance().compareTo(userAccountDTO.getBigDecimal()) > 0){
      userAccount.setAccountBalance(userAccount.getAccountBalance().subtract(userAccountDTO.getBigDecimal()));
      // 3. 更新账户
      userAccountMapper.updateById(userAccount);
     }
    // 4.远程调用转入微服务账户增加金额
    userAccountBank02Service.transferAmountToBank02(userAccountDTO);
   }
  
}

16.转出微服务编写控制层

@RestController
@RequestMapping("/userAccount")
public class UserAccountController {
  @Autowired
  private IUserAccountService iUserAccountService;
  /**
   * 转账
   * @return
   */
  @GetMapping("/transfer")
  public String transfer(UserAccountDTO userAccountDTO){
    iUserAccountService.transferAmountToBank02(userAccountDTO);
    return "转账成功";
   }
}

集成Hmily框架

1.转入和转出微服务引入依赖

 <dependency>
      <groupId>org.dromara</groupId>
      <artifactId>hmily-spring-boot-starter-apache-dubbo</artifactId>
      <version>2.1.1</version>
      <exclusions>
        <exclusion>
          <groupId>org.dromara</groupId>
          <artifactId>hmily-repository-mongodb</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

2.转入微服务编写hmily配置文件,在项目的 resource 新建文件名为:hmily.yml配置文件

hmily:
  server:
   configMode: local
   appName: user-account-bank02-dubbo
 #  如果server.configMode eq local 的时候才会读取到这里的配置信息.
  config:
   appName: user-account-bank01-dubbo
   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.cj.jdbc.Driver
   url : jdbc:mysql://192.168.66.100:3306/hmily?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&failOverReadOnly=false&useSSL=false&serverTimezone=UTC
   username: root
   password: 123456
   maxActive: 20
   minIdle: 10
   connectionTimeout: 30000
   idleTimeout: 600000
   maxLifetime: 1800000





====

3.转出微服务编写hmily配置文件,在项目的 resource 新建文件名为:hmily.yml配置文件

hmily:
  server:
   configMode: local
   appName: user-account-bank01-dubbo
 #  如果server.configMode eq local 的时候才会读取到这里的配置信息.
  config:
   appName: user-account-bank01-dubbo
   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.cj.jdbc.Driver
   url : jdbc:mysql://192.168.66.100:3306/hmily?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&failOverReadOnly=false&useSSL=false&serverTimezone=UTC
   username: root
   password: 123456
   maxActive: 20
   minIdle: 10
   connectionTimeout: 30000
   idleTimeout: 600000
   maxLifetime: 1800000

重点来了

4.转出微服务Try阶段

  /**
   * 转账功能
   * @param userAccountDTO
   */
  @HmilyTCC(confirmMethod = "sayConfrim", cancelMethod = "sayCancel")
  @Override
  public void transferAmountToBank02(UserAccountDTO userAccountDTO) {


    String txNo = userAccountDTO.getTxNo();
    log.info("**********  执行bank01 的 Try方法 ,事务id={}",txNo);


    // 1、 幂等处理
    TryLog tryLog = tryLogMapper.selectById(txNo);
    if (tryLog != null){
      return ;
     }
    // 2、 悬挂处理
    if (confirmLogMapper.selectById(txNo) != null || cancelLogMapper.selectById(txNo) != null){
      return ;
     }
     // 3. 根据账户编号查询账户信息
    UserAccount userAccount = baseMapper.selectById(userAccountDTO.getSourceAccountNo());
    // 4. 判断账户是否存在
    if (userAccount != null){
      // 5. 账户金额更新
      LambdaUpdateWrapper<UserAccount> ulw = new LambdaUpdateWrapper<>();
      // 更新转账金额
      ulw.set(UserAccount::getTransferAmount,userAccount.getTransferAmount().add(userAccountDTO.getBigDecimal()));
      // 更新余额
      ulw.set(UserAccount::getAccountBalance,userAccount.getAccountBalance().subtract(userAccountDTO.getBigDecimal()));
      ulw.eq(UserAccount::getAccountNo,userAccountDTO.getSourceAccountNo());
      baseMapper.update(null,ulw);
     }


    // 7. 准备阶段记录
    TryLog tryLog1 = new TryLog();
    tryLog1.setTxNo(txNo);
    tryLog1.setCreateTime(LocalDateTime.now());
    tryLogMapper.insert(tryLog1);


     // 8. 远程调用 转入微服务 跨库转账的功能
    userAccountBank02Service.transferAmountToBank02(userAccountDTO);


   }

5.转入微服务Try阶段

  /**
   * 跨库转账
   * @param userAccountDTO
   */
  @HmilyTCC(confirmMethod = "sayConfrim", cancelMethod = "sayCancel")
  @Override
  public void transferAmountToBank02(UserAccountDTO userAccountDTO) {
    String txNo = userAccountDTO.getTxNo();
    log.info("**********  执行bank02 的 Try方法 ,事务id={}",txNo);
    // 1、 幂等处理
    TryLog tryLog = tryLogMapper.selectById(txNo);
    if (tryLog != null){
      return ;
     }
    // 2、 悬挂处理
    if (confirmLogMapper.selectById(txNo) != null || cancelLogMapper.selectById(txNo) != null){
      return ;
     }
    // 3. 根据账户编号查询账户信息
    UserAccount userAccount = userAccountMapper.selectById(userAccountDTO.getTargetAccountNo());
    // 4. 判断账户是否存在
    if (userAccount != null){
      // 5. 账户金额更新
      LambdaUpdateWrapper<UserAccount> ulw = new LambdaUpdateWrapper<>();
      // 更新转账金额
      ulw.set(UserAccount::getTransferAmount,userAccount.getTransferAmount().add(userAccountDTO.getBigDecimal()));
      ulw.eq(UserAccount::getAccountNo,userAccountDTO.getTargetAccountNo());
      userAccountMapper.update(null,ulw);
     }
    // 7. 准备阶段记录
    TryLog tryLog1 = new TryLog();
    tryLog1.setTxNo(txNo);
    tryLog1.setCreateTime(LocalDateTime.now());
    tryLogMapper.insert(tryLog1);
   }

6.编写转出微服务Confirm阶段

/**
   * 确认阶段
   * @param userAccountDTO
   */
  public void sayConfrim(UserAccountDTO userAccountDTO) {


    String txNo = userAccountDTO.getTxNo();
    log.info("**********  执行bank01 的 Confrim方法 ,事务id={}",txNo);
    // 1、幂等处理
    ConfirmLog confirmLog = confirmLogMapper.selectById(txNo);
    if (confirmLog != null){
      return ;
     }
    // 2、根据账户id查询账户
    UserAccount userAccount = baseMapper.selectById(userAccountDTO.getSourceAccountNo());
    userAccount.setTransferAmount(userAccount.getTransferAmount().subtract(userAccountDTO.getBigDecimal()));
    baseMapper.updateById(userAccount);


    // 3、 确认日志记录


    ConfirmLog confirmLog1 = new ConfirmLog();
    confirmLog1.setTxNo(userAccountDTO.getTxNo());
    confirmLog1.setCreateTime(LocalDateTime.now());
    confirmLogMapper.insert(confirmLog1);


   }

7.编写转入微服务Confirm阶段

/**
   * 确认阶段
   * @param userAccountDTO
   */
  public void sayConfrim(UserAccountDTO userAccountDTO) {
    String txNo = userAccountDTO.getTxNo();
    log.info("**********  执行bank02 的 Confrim方法 ,事务id={}",txNo);
    // 1、幂等处理
    ConfirmLog confirmLog = confirmLogMapper.selectById(txNo);
    if (confirmLog != null) {
      return;
     }
    // 2、根据账户id查询账户
    UserAccount userAccount = userAccountMapper.selectById(userAccountDTO.getTargetAccountNo());
    userAccount.setAccountBalance(userAccount.getAccountBalance().add(userAccountDTO.getBigDecimal()));
    userAccount.setTransferAmount(userAccount.getTransferAmount().subtract(userAccountDTO.getBigDecimal()));
    userAccountMapper.updateById(userAccount);
    // 3、 确认日志记录
    ConfirmLog confirmLog1 = new ConfirmLog();
    confirmLog1.setTxNo(userAccountDTO.getTxNo());
    confirmLog1.setCreateTime(LocalDateTime.now());
    confirmLogMapper.insert(confirmLog1);
   }

8.转入微服务Cananl阶段

  /**
   * 回滚
   * @param userAccountDto
   */
  @Transactional(rollbackFor = Exception.class)
  public void cancelMethod(UserAccountDto userAccountDto){
    String txNo = userAccountDto.getTxNo();
    log.info("执行bank02的cancel方法,事务id:{}, 参数为:{}", txNo, JSONObject.toJSONString(userAccountDto));
    CancelLog cancelLog = iCancelLogService.findByTxNo(txNo);
    if(cancelLog != null){
      log.info("bank02已经执行过Cancel方法, txNo:{}", txNo);
      return;
     }
    // 保存记录
    iCancelLogService.saveCancelLog(txNo);


userAccountMapper.cancelUserAccountBalanceBank02(userAccountDto.getAmount(), userAccountDto.getTargetAccountNo());
   }

9.转出微服务Cancel阶段

 /**
   * 取消阶段
   * @param userAccountDTO
   */
  public void sayCancel(UserAccountDTO userAccountDTO) {
    String txNo = userAccountDTO.getTxNo();
    log.info("**********  执行bank01 的Cancel方法 ,事务id={}",txNo);
    // 1. 幂等处理
    CancelLog cancelLog = cancelLogMapper.selectById(txNo);
    if (cancelLog != null ){
      return;
     }
    // 2、根据账户id查询账户
    UserAccount userAccount = baseMapper.selectById(userAccountDTO.getSourceAccountNo());
    userAccount.setAccountBalance(userAccount.getAccountBalance().add(userAccountDTO.getBigDecimal()));
    userAccount.setTransferAmount(userAccount.getTransferAmount().subtract(userAccountDTO.getBigDecimal()));
    baseMapper.updateById(userAccount);
    // 3、记录回滚日志
    CancelLog cancelLog1 = new CancelLog();
    cancelLog1.setTxNo(txNo);
    cancelLog1.setCreateTime(LocalDateTime.now());
    cancelLogMapper.insert(cancelLog1);
   }

注:

  • 对@Hmily 标识的接口方法的具体实现上,加上@HmilyTCC(confirmMethod = “confirm”, cancelMethod = “cancel”)
  • confirmMethod : 确认方法名称,该方法参数列表与返回类型应与标识方法一致。
  • cancelMethod : 回滚方法名称,该方法参数列表与返回类型应与标识方法一致。
  • TCC模式应该保证 confirm 和 cancel 方法的幂等性,用户需要自行去开发这个2个方法,所有的事务的确认与回滚,完全由用户决定。Hmily框架只是负责来进行调用。

总结

提示:这里对文章进行总结:

Hmily-TCC分布式事务解决方案支持跨语言的场景,通过RPC实现跨语言的通信和协调。具体来说,Hmily-TCC分布式事务解决方案将分布式的问题转化为了本地的问题。在跨语言的场景中,每个参与者服务需要实现一个本地的事务处理器,该处理器负责实现对应服务的本地事务的执行、回滚和确认。
当一个全局事务开始时,Hmily-TCC分布式事务管理器会通过RPC调用将全局事务的信息发送给各个参与者服务。参与者服务的本地事务处理器会根据全局事务的指令,执行相应的本地事务操作,并将执行结果返回给Hmily-TCC分布式事务管理器。
当全局事务需要回滚时,Hmily-TCC分布式事务管理器会向各参与者服务发送回滚的指令。参与者服务的本地事务处理器会根据指令执行本地事务的回滚操作,并将回滚结果返回给Hmily-TCC分布式事务管理器。
在使用Hmily-TCC分布式事务解决方案时,可能会遇到一些常见问题或者坑,例如依赖冲突、数据一致性异常、分布式锁问题、性能问题和分布式事务可见性问题等。为了避免这些问题,需要在TCC接口的try、confirm和cancel方法中设计合适的异常处理机制,根据业务的需要选择事务回滚或者重试等处理方式。同时,还可以通过合理地设计TCC接口和实现类、缓存、异步等方式优化分布式事务的性能。
总的来说,Hmily是一个强大的TCC分布式事务解决方案,它通过RPC实现跨语言的通信和协调,从而实现了跨语言的分布式事务控制。在使用Hmily时,需要注意避免常见的问题和坑,并通过适当的优化措施来提高分布式事务的性能。

文章来源:https://blog.csdn.net/liubopro666/article/details/135083342
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。