大家好,又见面了,我是你们的朋友全栈君。
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
自带, 这样try
,confirm
,cancel
调用会落在同一个节点 充分利用了缓存,提搞了效率。在你的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远程调用。
- 调用account-server1接口,传入转账金额,扣除用户a账户
- account-server1远程调用account-server2,给用户b账户加钱
- 自行解决空回滚、悬挂、幂等性问题(目前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调用传播事务 try
,confirm
,cancel
方法的所有异常不要自行catch
任何异常都应该抛出给Hmily
框架处理- 消费者feign请求的本地接口如果存在fallback方法的话,要么保证fallback也会出现异常,要么不要fallback,否则异常无法被Hmily捕获
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/145851.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...