TCC的核心思想是:针对每一个操作都需要注册一个和其相对应的确认和补偿的操作,他分为三个阶段Try、Confirm和Cancel
A转账30元给B,A账户和B账户在不同银行(服务),当前余额都为100元
使用TTC事务,我们需要把之前实现的转账的代码拆分成三块,套到try-confirm-cancel中,由事务管理器(协调管理)推进AB两个try分别执行,在这个过程中,事务管理器会对AB进行监控,一旦任何一方出现了问题,就推进对方执行cancel;如果双方都没有异常,就推进AB执行confirm。如果在执行confirm或cancel过程中出现问题,就引入重试机制或由人工处理。
由此可见TCC解决分布式事务的缺点非常的明显:1、代码侵入性很强,改造成本很高;2、实现难度也不小,回滚策略实现并不简单。
Hmily是一个高性能分布式事务tcc开源框架。基于java语言来开发(JDK1.8),支持dubbo,springcloud,motan等rpc框架进行分布式事务。
https://dromara.org/website/zh-cn/docs/hmily/index.html
它目前支持以下特性:
数据库:MySQL 5.7.25+
JDK: jdk1.8+
微服务:spring-boot-2.1.3、spring-cloud-Greenwich.RELEASE
hmily:hmily-springcloud.2.0.4-RELEASE
CREATE DATABASE `bank1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
USE bank1;
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '户主姓名',
`account_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '银行卡号',
`account_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '帐户密码',
`account_balance` double NULL DEFAULT NULL COMMENT '帐户余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
INSERT INTO `account_info` VALUES (1, '张三', '1', '', 10000);
CREATE DATABASE `bank2` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
USE bank2;
DROP TABLE IF EXISTS `account_info`;
CREATE TABLE `account_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '户主姓名',
`account_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '银行卡号',
`account_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '帐户密码',
`account_balance` double NULL DEFAULT NULL COMMENT '帐户余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
INSERT INTO `account_info` VALUES (2, '李四', '2', NULL, 0);
(1)application.yml配置(只显示hmily部分)
org:
dromara:
hmily :
serializer : kryo #序列化工具
retryMax : 30 #最大重试次数
repositorySupport : db #持久化方式
started: true #事务发起方
hmilyDbConfig :
driverClassName : com.mysql.jdbc.Driver
url : jdbc:mysql://localhost:3306/bank1?useUnicode=true
username : root
password : root
(2)Hmily配置类
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class DatabaseConfiguration {
@Autowired
private Environment env;
@Bean
public HmilyTransactionBootstrap hmilyTransactionBootstrap(HmilyInitService hmilyInitService){
HmilyTransactionBootstrap hmilyTransactionBootstrap = new HmilyTransactionBootstrap(hmilyInitService);
hmilyTransactionBootstrap.setSerializer(env.getProperty("org.dromara.hmily.serializer"));
hmilyTransactionBootstrap.setRetryMax(Integer.parseInt(env.getProperty("org.dromara.hmily.retryMax")));
hmilyTransactionBootstrap.setRepositorySupport(env.getProperty("org.dromara.hmily.repositorySupport"));
hmilyTransactionBootstrap.setStarted(Boolean.parseBoolean(env.getProperty("org.dromara.hmily.started")));
HmilyDbConfig hmilyDbConfig = new HmilyDbConfig();
hmilyDbConfig.setDriverClassName(env.getProperty("org.dromara.hmily.hmilyDbConfig.driverClassName"));
hmilyDbConfig.setUrl(env.getProperty("org.dromara.hmily.hmilyDbConfig.url"));
hmilyDbConfig.setUsername(env.getProperty("org.dromara.hmily.hmilyDbConfig.username"));
hmilyDbConfig.setPassword(env.getProperty("org.dromara.hmily.hmilyDbConfig.password"));
hmilyTransactionBootstrap.setHmilyDbConfig(hmilyDbConfig);
return hmilyTransactionBootstrap;
}
}
(3)feign代理
@FeignClient(value = "hmily-demo-bank2")
public interface Bank2Client {
@GetMapping("/bank2/transfer")
@Hmily
Boolean transfer(@RequestParam("amount") Double amount);
}
(4)转账业务
@Service
public class AccountInfoTccImpl implements AccountInfoTcc {
@Autowired
private AccountInfoDao accountInfoDao;
@Autowired
private Bank2Client bank2Client;
@Override
@Hmily(confirmMethod = "commit", cancelMethod = "rollback")
public void prepare( String accountNo, double amount) {
System.out.println("...Bank1 Service prepare..." );
if(!bank2Client.transfer(amount)){
throw new RuntimeException("bank2 exception");
}
}
@Override
public void commit( String accountNo, double amount) {
System.out.println("...Bank1 Service commit..." );
}
@Override
public void rollback( String accountNo, double amount) {
accountInfoDao.updateAccountBalance(accountNo ,amount );
System.out.println("...Bank1 Service rollback..." );
}
}
注意:Try、Confirm、cancel的方法参数必须保持一致。
(5)启动类
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"cn.itxw.hmilydemo.bank1.feignClient"})
@ComponentScan({"cn.itxw.hmilydemo.bank1","org.dromara.hmily"})
public class Bank1HmilyServer {
public static void main(String[] args) {
SpringApplication.run(Bank1HmilyServer.class, args);
}
}
(1)application.yml配置(只显示hmily部分)
org:
dromara:
hmily :
serializer : kryo #序列化工具
retryMax : 30 #最大重试次数
repositorySupport : db #持久化方式
started: false #事务参与方
hmilyDbConfig :
driverClassName : com.mysql.jdbc.Driver
url : jdbc:mysql://localhost:3306/bank2?useUnicode=true
username : root
password : root
(2)Hmily配置类,和hmily-demo-bank1一样
(3)转账业务
@Service
public class AccountInfoServiceImpl implements AccountInfoService {
@Autowired
private AccountInfoDao accountInfoDao;
@Override
@Hmily(confirmMethod = "confirmMethod", cancelMethod = "cancelMethod")
public Boolean updateAccountBalance(String accountNo, Double amount) {
System.out.println("...Bank2 Service Begin ...");
try{
accountInfoDao.updateAccountBalance(accountNo ,amount);
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException( e.getMessage() );
}
return true;
}
public Boolean confirmMethod(String accountNo, Double amount) {
System.out.println("...Bank2 Service commit..." );
return true;
}
public Boolean cancelMethod(String accountNo, Double amount) {
accountInfoDao.updateAccountBalance(accountNo ,amount * -1);
System.out.println("...Bank2 Service rollback..." );
return true;
}
}
注意:Try、Confirm、cancel的方法参数必须保持一致。
(4)启动类
@SpringBootApplication(exclude = MongoAutoConfiguration.class)
@EnableDiscoveryClient
@ComponentScan({"cn.itxw.hmilydemo.bank2","org.dromara.hmily"})
public class Bank2HmilyServer {
public static void main(String[] args) {
SpringApplication.run(Bank2HmilyServer.class, args);
}
}