分布式事务专题-TCC(4)
1. 什么是TCC事务?
TCC是Try、Confirm、Cancel三个词语的缩写:
TCC要求每个分支事务实现三个操作:
预处理Try:
Try操作做业务检查及资源预留确认Confirm:
Confirm做业务确认操作撤销Cancel:
Cancel实现一个与Try相反的操作即回滚操作
- 正常情况
- 失败情况
执行过程:
- TM首先发起所有的分支事务的try操作
- 若try操作全部成功,TM将会发起所有分支事务的Confirm操作。
- 如果任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作.
其中Confirm/Cancel操作若执行失败,TM会进行重试
2. TCC解决方案
目前市面上的TCC框架众多比如下面这几种:
框架名称 | Gitbub地址 |
---|---|
tcc-transaction | https://github.com/changmingxie/tcc-transaction |
Hmily | https://github.com/yu199195/hmily |
ByteTCC | https://github.com/liuyangming/ByteTCC |
EasyTransaction | https://github.com/QNJR-GROUP/EasyTransaction |
上一节所讲的Seata也支持TCC,但Seata的TCC模式对Spring Cloud并没有提供支持
我们的目标是理解TCC的原 理以及事务协调运作的过程,因此更请倾向于轻量级易于理解的框架,因此最终确定了Hmily
Hmily是一个高性能分布式事务TCC开源框架。基于Java语言来开发(JDK1.8),支持Dubbo,Spring Cloud等RPC框架进行分布式事务。它目前支持以下特性.
- 支持嵌套事务(Nested transaction support).
- 采用disruptor框架进行事务日志的异步读写,与RPC框架的性能毫无差别。
- 支持SpringBoot-starter 项目启动,使用简单。
- RPC框架支持 : dubbo,motan,springcloud。
- 本地事务存储支持 : redis,mongodb,zookeeper,file,mysql。
- 事务日志序列化支持 :java,hessian,kryo,protostuff。
- 采用Aspect AOP 切面思想与Spring无缝集成,天然支持集群。
- RPC事务恢复,超时异常恢复等
Hmily利用AOP对参与分布式事务的本地方法与远程方法进行拦截处理,通过多方拦截,事务参与者能透明的调用到另一方的Try、Confirm、Cancel方法;传递事务上下文;并记录事务日志,酌情进行补偿,重试等。
Hmily不需要事务协调服务,但需要提供一个数据(mysql/mongodb/zookeeper/redis/file)来进行日志存
储。
TCC需要注意三种异常处理分别是空回滚、幂等、悬挂:
- 空回滚:
在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。
解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行,如果执行了,那就是正常回
滚;如果没执行,那就是空回滚
- 幂等
通过前面介绍已经了解到,为了保证TCC二阶段提交重试机制不会引发数据不一致,要求 TCC 的二阶段 Try、Confirm 和 Cancel 接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据 不一致等严重问题
解决思路在上述“分支事务记录”中增加执行状态,每次执行前都查询该状态。
- 悬挂
悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行
解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下,“分支事务记录”表中是否已经有二阶段事务记录,如果有则不执行Try
3. Hmily实现分布式事务
- maven依赖
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hmily‐springcloud</artifactId>
<version>2.0.4‐RELEASE</version>
</dependency>
- 配置hmily,application.yml
dromara:
hmily :
serializer : kryo
recoverDelayTime : 128
retryMax : 30
scheduledDelay : 128
scheduledThreadMax : 10
repositorySupport : db
started: true
hmilyDbConfig :
driverClassName : com.mysql.jdbc.Driver
url : jdbc:mysql://localhost:3306/bank?useUnicode=true
username : root
password : root
- 新增配置类接收application.yml中的Hmily配置信息,并创建HmilyTransactionBootstrap Bean:
@Bean
public HmilyTransactionBootstrap hmilyTransactionBootstrap(HmilyInitService hmilyInitService){
HmilyTransactionBootstrap hmilyTransactionBootstrap = new HmilyTransactionBootstrap(hmilyInitService);
hmilyTransactionBootstrap.setSerializer(env.getProperty("org.dromara.hmily.serializer"));
hmilyTransactionBootstrap.setRecoverDelayTime(Integer.parseInt(env.getProperty("org.dromara.hmily.recoverDelayTime")));
hmilyTransactionBootstrap.setRetryMax(Integer.parseInt(env.getProperty("org.dromara.hmily.retryMax")));
hmilyTransactionBootstrap.setScheduledDelay(Integer.parseInt(env.getProperty("org.dromara.hmily.scheduledDelay")));
hmilyTransactionBootstrap.setScheduledThreadMax(Integer.parseInt(env.getProperty("org.dromara.hmily.scheduledThreadMax")));
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;
}
- 启动类增加@EnableAspectJAutoProxy并增加org.dromara.hmily的扫描项:
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients(basePackages = {"cn.itcast.dtx.tccdemo.bank1.spring"})
@ComponentScan({"cn.itcast.dtx.tccdemo.bank1","org.dromara.hmily"})
public class Bank1HmilyServer {
public static void main(String[] args) {
SpringApplication.run(Bank1HmilyServer.class, args);
}
}
4. 小结
如果拿TCC事务的处理流程与2PC两阶段提交做比较,2PC通常都是在跨库的DB层面,而TCC则在应用层面的处理,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能.
而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。