Hmily实现TCC分布式事务
系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
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时,需要注意避免常见的问题和坑,并通过适当的优化措施来提高分布式事务的性能。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!