个人博客网:https://wushaopei.github.io/    (你想要这里多有)

1.什么是TCC事务

TCC是Try、Confifirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confifirm、撤销Cancel。Try操作做业务检查及资源预留,Confifirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confifirm操作,其中Confifirm/Cancel操作若执行失败,TM会进行重试。
 
             
分支事务失败的情况:
             
TCC分为三个阶段:
 
1. Try 阶段是做业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的Confifirm 一起才能真正构成一个完整的业务逻辑。
2. Confifirm 阶段是做确认提交,Try阶段所有分支事务执行成功后开始执行 Confifirm。通常情况下,采用TCC则认为 Confifirm阶段是不会出错的。即:只要Try成功,Confifirm一定成功。若Confifirm阶段真的出错了,需引入重试机制或人工处理。
 
3. Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采用TCC则认为Cancel阶段也是一定成功的。若Cancel阶段真的出错了,需引入重试机制或人工处理。
 
4. TM事务管理器
TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当TM的角色,TM独立出来是为了成为公用组件,是为了考虑系统结构和软件复用。
 
TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下文,
追踪和记录状态,由于Confifirm 和cancel失败需进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求
多少次,其结果都相同。
 
 

2.TCC 解决方案

目前市面上的TCC框架众多比如下面这几种:
 
(以下数据采集日为2019年07月11日)
 
框架名称
Gitbub地址
star数量
tcc-transaction
https://github.com/changmingxie/tcc-transaction
3850
Hmily
https://github.com/yu199195/hmily
2407
ByteTCC
https://github.com/liuyangming/ByteTCC
EasyTransaction
https://github.com/QNJR-GROUP/EasyTransaction
1690
上一节所讲的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,fifile,mysql。
  • 事务日志序列化支持 :java,hessian,kryo,protostuffff。
  • 采用Aspect AOP 切面思想与Spring无缝集成,天然支持集群。
  • RPC事务恢复,超时异常恢复等。
Hmily利用AOP对参与分布式事务的本地方法与远程方法进行拦截处理,通过多方拦截,事务参与者能透明的调用到另一方的Try、Confifirm、Cancel方法;传递事务上下文;并记录事务日志,酌情进行补偿,重试等。
 
Hmily不需要事务协调服务,但需要提供一个数据库(mysql/mongodb/zookeeper/redis/fifile)来进行日志存储。
 
Hmily实现的TCC服务与普通的服务一样,只需要暴露一个接口,也就是它的Try业务。Confifirm/Cancel业务逻辑,只是因为全局事务提交/回滚的需要才提供的,因此Confifirm/Cancel业务只需要被Hmily TCC事务框架发现即可,不需要被调用它的其他业务服务所感知。

官网介绍:https://dromara.org/website/zh-cn/docs/hmily/index.html

TCC需要注意三种异常处理分别是空回滚、幂等、悬挂:

空回滚
 

在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。

出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。

解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道一阶段是否执行,如果执行了,那就是正常回滚;如果没执行,那就是空回滚。前面已经说过TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条。再额外增加一张分支事务记录表,其中有全局事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。
 
幂等
 

通过前面介绍已经了解到,为了保证TCC二阶段提交重试机制不会引发数据不一致,要求 TCC 的二阶段 Try、Confifirm 和 Cancel 接口保证幂等,这样不会重复使用或者释放资源。如果幂等控制没有做好,很有可能导致数据不一致等严重问题。解决思路在上述“分支事务记录”中增加执行状态,每次执行前都查询该状态。

悬挂
 
悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
 

出现原因是在 RPC 调用分支事务try时,先注册分支事务,再执行RPC调用,如果此时 RPC 调用的网络发生拥堵,通常 RPC 调用是有超时时间的,RPC 超时以后,TM就会通知RM回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行,而一个 Try 方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后没法继续处理。

解决思路是如果二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下,“分支事务记录”表中是否已经有二阶段事务记录,如果有则不执行Try。

举例,场景为 A 转账 30 元给 BAB账户在不同的服务。

方案1
账户A
 
  1. try
  2. 检查余额是否够30
  3. 扣减30
  4. confirm

  5. cancel
  6. 增加30

账户B

  1. try
  2. 增加30
  3. confirm

  4. cancel
  5. 减少30

方案1说明:
 
1)账户A,这里的余额就是所谓的业务资源,按照前面提到的原则,在第一阶段需要检查并预留业务资源,因此,我们在扣钱 TCC 资源的 Try 接口里先检查 A 账户余额是否足够,如果足够则扣除 30 元。 Confifirm 接口表示正式提交,由于业务资源已经在 Try 接口里扣除掉了,那么在第二阶段的 Confifirm 接口里可以什么都不用做。Cancel接口的执行表示整个事务回滚,账户A回滚则需要把 Try 接口里扣除掉的 30 元还给账户。
2)账号B,在第一阶段 Try 接口里实现给账户B加钱,Cancel 接口的执行表示整个事务回滚,账户B回滚则需要把Try 接口里加的 30 元再减去。
 
方案1的问题分析:
 
1)如果账户A的try没有执行在cancel则就多加了30元。
2)由于try,cancel、confifirm都是由单独的线程去调用,且会出现重复调用,所以都需要实现幂等。
3)账号B在try中增加30元,当try执行完成后可能会其它线程给消费了。
4)如果账户B的try没有执行在cancel则就多减了30元。
问题解决:
 
1)账户A的cancel方法需要判断try方法是否执行,正常执行try后方可执行cancel。
2)try,cancel、confifirm方法实现幂等。
3)账号B在try方法中不允许更新账户金额,在confifirm中更新账户金额。
4)账户B的cancel方法需要判断try方法是否执行,正常执行try后方可执行cancel。
优化方案
账户A
 
  1. try
  2. try幂等校验
  3. try悬挂处理
  4. 检查余额是否够30
  5. 扣减30
  6. confirm

  7. cancel
  8. cancel幂等校验
  9. cancel空回滚处理
  10. 增加可用余额30

账户B

  1. try

  2. confirm
  3. confirm幂等校验
  4. 正式增加30
  5. cancel

3、Hmily实现TCC事务

3.1.业务说明

本实例通过Hmily实现TCC分布式事务,模拟两个账户的转账交易过程。
 
两个账户分别在不同的银行(张三在bank1、李四在bank2),bank1、bank2是两个微服务。交易过程是,张三给李四转账指定金额。
 
上述交易步骤,要么一起成功,要么一起失败,必须是一个整体性的事务。

3.2.程序组成部分

数据库:MySQL-5.7.25
 
JDK:64位 jdk1.8.0_201
 
微服务:spring-boot-2.1.3、spring-cloud-Greenwich.RELEASE
 
Hmily:hmily-springcloud.2.0.4-RELEASE
 
 
微服务及数据库的关系 :
 

dtx/dtx-tcc-demo/dtx-tcc-demo-bank1 银行1,操作张三账户, 连接数据库bank1

dtx/dtx-tcc-demo/dtx-tcc-demo-bank2 银行2,操作李四账户,连接数据库bank2

服务注册中心:dtx/discover-server
 

3.3.创建数据库

导入数据库脚本:资料\sql\bank1.sql、资料\sql\bank2.sql、已经导过不用重复导入。
 
创建hmily数据库,用于存储hmily框架记录的数据。
 
  1. CREATE DATABASE `hmily` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

创建bank1库,并导入以下表结构和数据(包含张三账户)
 
  1. CREATE DATABASE `bank1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

  1. DROP TABLE IF EXISTS `account_info`;
  2. CREATE TABLE `account_info` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  4. `account_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '户 主姓名',
  5. `account_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '银行 卡号',
  6. `account_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '帐户密码',
  7. `account_balance` double NULL DEFAULT NULL COMMENT '帐户余额',
  8. PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
  9. INSERT INTO `account_info` VALUES (2, '张三的账户', '1', '', 10000);

创建bank2库,并导入以下表结构和数据(包含李四账户)
 
  1. CREATE DATABASE `bank2` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

  1. CREATE TABLE `account_info` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `account_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '户 主姓名',
  4. `account_no` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '银行 卡号',
  5. `account_password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '帐户密码',
  6. `account_balance` double NULL DEFAULT NULL COMMENT '帐户余额',
  7. PRIMARY KEY (`id`) USING BTREE
  8. ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
  9. INSERT INTO `account_info` VALUES (3, '李四的账户', '2', NULL, 0);

每个数据库都创建try、confifirm、cancel三张日志表:
 
  1. CREATE TABLE `local_try_log` (
  2. `tx_no` varchar(64) NOT NULL COMMENT '事务id',
  3. `create_time` datetime DEFAULT NULL, PRIMARY KEY (`tx_no`)
  4. ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  5. CREATE TABLE `local_confirm_log` (
  6. `tx_no` varchar(64) NOT NULL COMMENT '事务id',
  7. `create_time` datetime DEFAULT NULL
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  9. CREATE TABLE `local_cancel_log` (
  10. `tx_no` varchar(64) NOT NULL COMMENT '事务id',
  11. `create_time` datetime DEFAULT NULL,
  12. PRIMARY KEY (`tx_no`)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8

3.5 discover-server
 
discover-server是服务注册中心,测试工程将自己注册至discover-server。
 
导入:资料\基础代码\dtx 父工程,此工程自带了discover-server,discover-server基于Eureka实现。
 
已经导过不用重复导入。
3.6 导入案例工程dtx-tcc-demo
 
dtx-tcc-demo是tcc的测试工程,根据业务需求需要创建两个dtx-tcc-demo工程。
 
(1)导入dtx-tcc-demo
 
导入:资料\基础代码\dtx-tcc-demo到父工程dtx下。
 
两个测试工程如下:
 
dtx/dtx-tcc-demo/dtx-tcc-demo-bank1 银行1,操作张三账户,连接数据库bank1
 
dtx/dtx-tcc-demo/dtx-tcc-demo-bank2 银行2,操作李四账户,连接数据库bank2
 
(2)引入maven依赖
 
(3)配置hmily
application.yml:
 
 
新增配置类接收application.yml中的Hmily配置信息,并创建HmilyTransactionBootstrap Bean:
 
 
 
启动类增加@EnableAspectJAutoProxy并增加org.dromara.hmily的扫描项:
 
 
3.7 dtx-tcc-demo-bank1
 
dtx-tcc-demo-bank1实现try和cancel方法,如下:
 
 
1)Dao
 
 
2)try和cancel方法
 
3)feignClient
 
 
 
4)Controller
 
 
 
4.3.8dtx-tcc-demo-bank2
 
dtx-tcc-demo-bank2实现如下功能:
 
1)Dao
 
 
2)实现confifirm方法
 
 
3)Controller
 
3.3.9 测试场景
  • 张三向李四转账成功。
  • 李四事务失败,张三事务回滚成功。
  • 张三事务失败,李四分支事务回滚成功。
  • 分支事务超时测试

4、小结

如果拿TCC事务的处理流程与2PC两阶段提交做比较,2PC通常都是在跨库的DB层面,而TCC则在应用层面的处理,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能

而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confifirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。

分布式事务专题笔记(三)分布式事务解决方案之TCC(三阶段提交)的更多相关文章

  1. 分布式事务专题笔记(二)分布式事务解决方案之 2PC(两阶段提交)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 前面已经了解了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界常见的解决方案有2PC.TCC ...

  2. 分布式事务专题笔记(一) 基础概念 与 CAP 理论

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.基础概念 1.什么是事务 什么是事务?举个生活中的例子:你去小卖铺买东西,“一手交钱,一手交货”就是 ...

  3. 分布式事务之解决方案(TCC)

    4. 分布式事务解决方案之TCC 4.1. 什么是TCC事务 TCC是Try.Confirm.Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作 :预处理Try.确认Confirm.撤销C ...

  4. 分布式事务 & 两阶段提交 & 三阶段提交

    可以参考这篇文章: http://blog.csdn.net/whycold/article/details/47702133 两阶段提交保证了分布式事务的原子性,这些子事务要么都做,要么都不做. 而 ...

  5. 分布式事务(一)两阶段提交及JTA

    原创文章,同步发自作者个人博客 http://www.jasongj.com/big_data/two_phase_commit/ 分布式事务 分布式事务简介 分布式事务是指会涉及到操作多个数据库(或 ...

  6. Mysql事务学习笔记

    Mysql事务学习笔记 1.事务概述 事务是数据库的执行单元,它包含了一条或多条sql语句,进行的操作是要么全部执行,要么全部都不执行. 2.事务执行命令 语法格式: start transactio ...

  7. 分布式:分布式事务(CAP、两阶段提交、三阶段提交)

    1 关于分布式系统 1.1 介绍 我们常见的单体结构的集中式系统,一般整个项目就是一个独立的应用,所有的模块都聚合在一起.明显的弊端就是不易扩展.发布冗重.服务治理不好做. 所以我们把整个系统拆分成若 ...

  8. spring分布式事务学习笔记

    最近项目中使用了分布式事务,本文及接下来两篇文章总结一下在项目中学到的知识. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distributed t ...

  9. spring分布式事务学习笔记(1)

    此文已由作者夏昀授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distrib ...

随机推荐

  1. Coursera课程笔记----Write Professional Emails in English----Week 2

    Let's Start Writing (Week 2) Write Effective Subject Lines be BRIEF 50 characters or less = 5-7 word ...

  2. Spring 循环引用(三)源码深入分析版

    @ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSingleton b ...

  3. 【MySQL基础总结】运算符的使用

    运算符的使用 算数运算符 比较运算符 结果只能为TRUE(1)或FALSE(0) 逻辑运算符 运算符的优先级 可以通过括号改变优先级 示例 算数运算符 比较运算符 逻辑运算符

  4. VA01销售订单批导问题解决

    1业务场景 事务代码:VA01创建销售订单,VA02修改销售订单 可以通过BAPI_SALESORDER_CREATEFROMDAT2批量创建 可以通过BAPI_SALESORDER_CHANGE批量 ...

  5. hex文件格式总结

    hex文件格式总结 文章目录 hex文件格式总结 什么是hex文件? 文件格式 指令类型(Record type) 校验和 :04 02B0 00 92020008 AE :04 0000 05 08 ...

  6. {bzoj2338 [HNOI2011]数矩形 && NBUT 1453 LeBlanc}平面内找最大矩形

    思路: 枚举3个点,计算第4个点并判断是否存在,复杂度为O(N3logN)或O(N3α) 考虑矩形的对角线,两条对角线可以构成一个矩形,它们的长度和中点必须完全一样,于是将所有线段按长度和中点排序,那 ...

  7. [CodeForces 344C Rational Resistance]YY,证明

    题意:给若干个阻值为1的电阻,要得到阻值为a/b的电阻最少需要多少个. 思路:令a=mb+n,则a/b=m+n/b=m+1/(b/n),令f(a,b)表示得到a/b的电阻的答案,由f(a,b)=f(b ...

  8. IDEA 创建Spring项目后org.springframework.boot报错

    IDEA 创建 Spring boot 项目后 ,在pom.xml文件中 org.springframework.boot出错,刷新也没有作用. 如图: 可以降低 org.springframewor ...

  9. C++内存管理学习笔记(3)

    /****************************************************************/ /*            学习是合作和分享式的! /* Auth ...

  10. 适用于任何Html内容的jQuery Slider插件 - AnySlider

    任何Slider都是一个易于使用且支持触摸的jQuery插件,允许您为任何html内容创建可自定义的滑块,如图像,文本,视频等. 特征: 重量轻,易于使用 支持键盘导航 使用淡入淡出或幻灯片过渡以及自 ...