事务回滚什么意思try_分布式事务之解决方案(TCC)

更新时间:2023-05-11 02:30:11 阅读: 评论:0

事务回滚什么意思try_分布式事务之解决⽅案(TCC)
4. 分布式事务解决⽅案之TCC
4.1. 什么是TCC事务
TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分⽀事务实现三个操作 :预处理Try、确认Confirm、撤销Cancel。Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现⼀个与Try相反的操作既回滚操作。TM⾸先发起所有的分⽀事务的try操作,任何⼀个分⽀事务的try操作执⾏失败,TM将会发起所有分⽀事务的Cancel操作,若try操作全部成功,TM将会发起所有分⽀事务的Confirm操作,其中Confirm/Cancel操作若执⾏失败,TM会进⾏重试。
分⽀事务失败的情况 :
TCC分为三个阶段 :
1. Try阶段是做业务检查(⼀致性)及资源预留(隔离),此阶段仅是⼀个初步操作,它和后续的Confirm⼀起才能真正构成⼀个完整的
业务逻辑。
2. Confirm阶段是做确认提交,Try阶段所有分⽀事务执⾏成功后开始执⾏Confirm。通常情况下,采⽤TCC则认为Confirm阶段是不
会出错的。即 :只要Try成功,Confirm⼀定成功。若Confirm阶段真的出错了,需引⼊重试机制或⼈⼯处理。
3. Cancel阶段是在业务执⾏错误需要回滚的状态下执⾏分⽀事务的业务取消,预留资源释放。通常情况下,采⽤TCC则认为Cancel阶段
也是⼀定成功的。若Cancel阶段真的出错了,需引⼊重试机制或⼈⼯处理。
4. TM事务管理器
TM事务管理器可以实现为独⽴的服务,也可以让全局事务发起⽅充当TM的⾓⾊,TM独⽴出来是为了成为公⽤组件,是为了考虑结构和软件复⽤。
TM在发起全局事务时⽣成全局事务记录,全局事务ID贯穿整个分布式事务调⽤链条,⽤来记录事务上下⽂,追踪和记录状态,由于Confirm和Cancel失败需进⾏重试,因此需要实现为幂等性是指同⼀个操作⽆论请求多少次,其结果都相同。
4.2. TCC解决⽅案
⽬前市⾯上的TCC框架众多⽐如下⾯这⼏种 :
Seata也⽀持TCC,但Seata的TCC模式对Spring Cloud并没有提供⽀持。我们的⽬标是理解TCC原理以及事务协调运作的过程,因此更倾向于轻量级易于理解的框架。
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)来进⾏⽇志存储。
Hmily实现的TCC服务与普通的服务⼀样,只需要暴露⼀个接⼝,也就是它的Try业务。Confirm/Cancel业务逻辑,只是因为全局事务提交/回滚的需要才提供的,因此Confirm/Cancel业务只需要被Hmily TCC事务框架发现即可,不需要被调⽤它的其他业务服务所感知。
TCC需要注意三种异常处理分别是空回滚、幂等、悬官⽹介绍 :/website/zh-cn/docs/hmily/index.htmlTCC需要注意三种异常处理分别是空回滚、幂等、悬挂 :空回滚
空回滚 :
挂 :
在没有调⽤TCC资源Try⽅法的情况下,调⽤来⼆阶段的Cancel⽅法,Cancel⽅法需要识别出这是⼀个空回滚,然后直接返回成功。
出现原因是当⼀个分⽀事务所在服务宕机或⽹络异常,分⽀事务调⽤记录为失败,这个时候其实是没有执⾏Try阶段,当故障恢复后,分布式事务进⾏回滚则会调⽤⼆阶段的Cancel⽅法,从⽽形成空回滚。
解决思路是关键就是要识别出这个空回滚。思路很简单就是需要知道⼀阶段是否执⾏,如果执⾏来,那就是正常回滚;如果没执⾏,那就是空回滚。前⾯已经说过TM在发起全局事务时⽣成全局事务记录,全局事务ID贯穿整个分布式事务调⽤链条。再额外增加⼀张分⽀事务记录表,其中有全局事务ID和分⽀事务ID,第⼀阶段Try⽅法⾥会插⼊⼀条记录,表⽰⼀阶段执⾏来。Cancel接⼝⾥读取该记录,
幂等 :
如果该记录存在,则正常回滚;如果该记录不存在,则是空回滚。幂等
通过前⾯介绍已经了解到,为了保证TCC⼆阶段提交重试机制不会引发数据不⼀致,要求TCC的⼆阶段Try、Confirm和Cancel接⼝保证幂等,这样不会重复使⽤或者释放资源。如果幂等控制没有做好,很有可能导致数据不⼀致等严重问题。
悬挂 :
解决思路在上述 “分⽀事务记录”中增加执⾏状态,每次执⾏前都查询该状态。悬挂
悬挂就是对于⼀个分布式事务,其⼆阶段Cancel接⼝⽐Try接⼝先执⾏。
出现原因是在RPC调⽤分⽀事务try时,先注册分⽀事务,再执⾏RPC调⽤,如果此时RPC调⽤的⽹络发⽣拥堵,通常RPC调⽤是有超时时间的,RPC超时以后,TM就会通知RM回滚该分布式事务,可能回滚完成后,RPC请求才到达参与者真正执⾏,⽽⼀个Try⽅法预留的业务资源,只有该分布式事务才能使⽤,该分布式事务第⼀阶段预留的业务资源就再也没有⼈能够处理了,对于这种情况,我们就称为悬挂,即业务资源预留后⽆法继续处理。
解决思路是如果⼆阶段执⾏完成,那⼀阶段就不能再继续执⾏。在执⾏⼀阶段事务时判断在该全局事务下,“分⽀事务记录”表中是否
⽅案 1 :
举例,场景为A转账30元给B,A和B账户在不同的服务。⽅案 1已经有⼆阶段事务记录,如果有则不执⾏Try。举例,场景为A转账30元给B,A和B账户在不同的服务。
账户A
try: 检查余额是否够30元 扣减30元 confirm: 空 cancel: 增加30元
账户B
try: 增加30元 confirm: 空 cancel: 减少30元
⽅案1说明:
1)账户A,这⾥的余额就是所谓的业务资源,按照前⾯提到的原则,在第⼀阶段需要检查并预留业务资源,因此, 我们在扣钱 TCC 资源的Try 接⼝⾥先检查 A 账户余额是否⾜够,如果⾜够则扣除 30 元。 Confirm 接⼝表⽰正式 提交,由于业务资源已经在 Try 接⼝⾥扣除掉了,那么在第⼆阶段的 Confirm 接⼝⾥可以什么都不⽤做。Cancel 接⼝的执⾏表⽰整个事务回滚,账户A回滚则需要把 Try 接⼝⾥扣除掉的 30 元还给账户。
2)账号B,在第⼀阶段 Try 接⼝⾥实现给账户B加钱,Cancel 接⼝的执⾏表⽰整个事务回滚,账户B回滚则需要把 Try 接⼝⾥加的 30 元再
⽅案1的问题分析:
减去。⽅案1的问题分析:
1)如果账户A的try没有执⾏在cancel则就多加了30元。
2)由于try,cancel、confirm都是由单独的线程去调⽤,且会出现重复调⽤,所以都需要实现幂等。
3)账号B在try中增加30元,当try执⾏完成后可能会其它线程给消费了。
问题解决:
4)如果账户B的try没有执⾏在cancel则就多减了30元。问题解决:
1)账户A的cancel⽅法需要判断try⽅法是否执⾏,正常执⾏try后⽅可执⾏cancel。
2)try、cancel、confirm⽅法实现幂等。
3)账户B在try⽅法中不允许更新账户⾦额,在confirm中更新账户⾦额。
优化⽅案:
4)账户B的cancel⽅法需要判断try⽅法是否执⾏,正常执⾏try后⽅可执⾏cancel。优化⽅案:
账户A :
try: try幂等校验 try悬挂处理 检查余额是否够30元 扣减30元 confirm: 空 cancel: cancel幂等校验 cancel空回滚处理 增加可⽤余额30元
账户B :
try: 空 confirm: confirm幂等校验 正式增加30元 cancel: 空
4.3. Hmily实现TCC事务
4.3.1.业务说明
通过Hmily实现TCC分布式事务,模拟两个账户的转账交易过程。
两个账户分别在不同的银⾏(张三在bank1、李四在bank2),bank1、bank2是两个微服务。交易过程是,张三给李四转账制定⾦额。上述交易步骤,要么⼀起成功,要么⼀起失败,必须是⼀个整体性
事务。
4.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-rver
4.3.3. 创建数据库
创建hmily数据库,⽤于存储hmily框架记录的数据。
创建bank1库,并导⼊以下表结构和数据CREATE DATABASE hmily CHARACTER SET ‘utf8’ COLLATE ‘utf8_general_ci’;创建bank1库,并导⼊以下表结构和数据
(包含张三账户)
创建bank2库,并导⼊以下表结构和数据CREATE DATABASE bank1 CHARACTER SET ‘utf8’ COLLATE ‘utf8_general_ci’;创建bank2库,并导⼊以下表结构和数据
(包含李四账户)
CREATE DATABASE bank2 CHARACTER SET ‘utf8’ COLLATE ‘utf8_general_ci’;
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, ‘张三的账户’, ‘1’, ‘’, 10000);
每个数据库都创建try、confirm、cancel三张⽇志表:
CREATE TABLE `local_try_log` ( `tx_no` varchar(64) NOT NULL COMMENT `create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 CREATE TABLE `local_confirm_log` ( `tx_no`
varchar(64) NOT NULL COMMENT `create_time` datetime DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `local_cancel_log` ( `tx_no` varchar(64) NOT NULL COMMENT `create_time` datetime DEFAULT NULL,
PRIMARY KEY (`tx_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
4.3.5 ⼯程dtx-tcc-demo
(1)引⼊maven依赖
<dependency> <groupId>org.dromara</groupId> <artifactId>hmily‐springcloud</artifactId> <version>2.0.4‐
RELEASE</version> </dependency>
(2)配置hmily
org: dromara: hmily : rializer : kryo recoverDelayTime : 128 retryMax : 30 scheduledDelay : 128 scheduledThreadMax : 10 repositorySupport : db started: true hmilyDbConfig : driverClassName : sql.jdbc.Driver url :
jdbc:mysql://localhost:3306/bank?uUnicode=true urname : root password : root
新增配置类接收l中的Hmily配置信息,并创建HmilyTransactionBootstrap Bean:
@Bean public HmilyTransactionBootstrap hmilyTransactionBootstrap(HmilyInitService hmilyInitService){ HmilyTransactionBootstrap hmilyTransactionBootstrap = new HmilyTransactionBootstrap(hmilyInitService); hmilyTransactionBootstrap.Property("org.dromara.hmily.rializer"));
hmilyTransactionBootstrap.tRecoverDelayTime(Integer.Property("org.verDelayTime")));
hmilyTransactionBootstrap.tRetryMax(Integer.Property("org.Max"))); hmilyTransactionBootstrap.tScheduledDelay(Integer.Property("org.dromara.hmily.scheduledDelay"))); hmilyTransactionBootstrap.tScheduledThreadMax(Integer.Property("org.dromara.hmily.scheduledThreadMax"))); hmilyTransactionBootstrap.Property("org.positorySupport")); hmilyTransactionBootstrap.tStarted(Boolean.Property("org.dromara.hmily.started"))); HmilyDbConfig hmilyDbConfig = new HmilyDbConfig();
hmilyDbConfig.Property("org.dromara.hmily.hmilyDbConfig.driverClassName"));
hmilyDbConfig.Property("org.dromara.hmily.hmilyDbConfig.url"));
hmilyDbConfig.Property("org.dromara.hmily.hmilyDbConfig.urname"));
hmilyDbConfig.Property("org.dromara.hmily.hmilyDbConfig.password"));
hmilyTransactionBootstrap.tHmilyDbConfig(hmilyDbConfig); return hmilyTransactionBootstrap; }
启动类增加@EnableAspectJAutoProxy并增加org.dromara.hmily的扫描项:
@SpringBootApplication @EnableDiscoveryClient @EnableHystrix @EnableFeignClients(baPackages =
{"cn.demo.bank1.spring"}) @ComponentScan({"cn.demo.bank1","org.dromara.hmily"}) public class Bank1HmilyServer { public static void main(String[] args) { SpringApplication.run(Bank1HmilyServer.class, args); } }
4.3.7 dtx-tcc-demo-bank1
dtx-tcc-demo-bank1实现try和cancel⽅法,如下 :
try: try幂等校验 try悬挂处理 检查余额是够扣减⾦额 扣减⾦额 confirm: 空 cancel: cancel幂等校验 cancel空回滚处理 增加可⽤余额
1. Dao
@Mapper @Component public interface AccountInfoDao { @Update("update account_info t
account_balance=account_balance - #{amount} where account_balance>=#{amount} and account_
no=#{accountNo} ") int subtractAccountBalance(@Param("accountNo") String accountNo, @Param("amount") Double amount); @Update("update account_info t account_balance=account_balance + #{amount} where account_no=#{accountNo} ") int addAccountBalance(@Param("accountNo") String accountNo, @Param("amount") Double amount); /** * 增加某分⽀事务try执⾏记录 * @param localTradeNo 本地事务编号 * @return */ @Inrt("inrt into local_try_log values(#{txNo},now());") int
addTry(String localTradeNo); @Inrt("inrt into local_confirm_log values(#{txNo},now());") int addConfirm(String localTradeNo); @Inrt("inrt into local_cancel_log values(#{txNo},now());") int addCancel(String localTradeNo); /** * 查询分⽀事务try是否已执⾏ * @param localTradeNo 本地事务编号 * @return */ @Select("lect count(1) from local_try_log where
tx_no = #{txNo} ") int isExistTry(String localTradeNo); /** * 查询分⽀事务confirm是否已执⾏ * @param localTradeNo 本地事务编号 * @return */ @Select("lect count(1) from local_confirm_log where tx_no = #{txNo} ") int isExistConfirm(String localTradeNo); /** * 查询分⽀事务cancel是否已执⾏ * @param localTradeNo 本地事务编号 * @return */ @Select("lect count(1) from local_cancel_log where tx_no = #{txNo} ") int isExistCancel(String localTradeNo); }
2)try和cancel⽅法
3)feignClient
@FeignClient(value = "ata-demo-bank2", fallback = Bank2Client.class) public interface Bank2Client {
@GetMapping("/bank2/transfer") @Hmily Boolean transfer(@RequestParam("amount") Double amount); }
1. Controller
@RestController public class Bank1Controller { @Autowired private AccountInfoService accountInfoService;
@RequestMapping("/transfer") public String test(@RequestParam("amount") Double amount) {
accountInfoService.updateAccountBalance("1", amount); return "bank1" + amount; } }
4.3.8 dtx-tcc-demo-bank2
dtx-tcc-demo-bank2实现如下功能 :
try: 空 confirm: confirm幂等校验 正式增加⾦额 cancel: 空
1)Dao
@Component @Mapper public interface AccountInfoDao { @Update("update account_info t
account_balance=account_balance + #{amount} where account_no=#{accountNo} ") int
addAccountBalance(@Param("accountNo") String accountNo, @Param("amount") Double amount); /** * 增加某分⽀事务try执⾏记录 * @param localTradeNo 本地事务编号 * @return */ @Inrt("inrt into local_try_log values(#{txNo},now());") int
addTry(String localTradeNo); @Inrt("inrt into local_confirm_log values(#{txNo},now());") int addConfirm(String localTradeNo); @Inrt("inrt into local_cancel_log values(#{txNo},now());") int addCancel(String localTradeNo); /** * 查询分⽀事务try是否已执⾏ * @param localTradeNo 本地事务编号 * @return */ @Select("lect count(1) from local_try_log where
tx_no = #{txNo} ") int isExistTry(String localTradeNo); /** * 查询分⽀事务confirm是否已执⾏ * @param localTradeNo 本地事务编号 * @return */ @Select("lect count(1) from local_confirm_log where tx_no = #{txNo} ") int isExistConfirm(String localTradeNo); /** * 查询分⽀事务cancel是否已执
⾏ * @param localTradeNo 本地事务编号 * @return */ @Select("lect count(1) from local_cancel_log where tx_no = #{txNo} ") int isExistCancel(String localTradeNo); }
2)实现confirm⽅法

本文发布于:2023-05-11 02:30:11,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/881394.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:事务   阶段   需要   回滚   业务
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图