分布式事务解决方案之二:TCC

(自学笔记)
------------------
4.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 阶段是做业务检查 ( 一致性 ) 及资源预留 ( 隔离 ) ,此阶段仅是一个初步操作,它和后续的 Confirm 一起才能
真正构成一个完整的业务逻辑
2. Confirm 阶段是做确认提交, Try 阶段所有分支事务执行成功后开始执行 Confirm 。通常情况下,采用 TCC
认为 Confifirm 阶段是不会出错的。即:只要 Try 成功, Confirm 一定成功。若 Confirm 阶段真的出错了,需引
入重试机制或人工处理。
3. Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,采
TCC 则认为 Cancel 阶段也是一定成功的。若 Cancel 阶段真的出错了,需引入重试机制或人工处理。
4. TM 事务管理器
TM 事务管理器可以实现为独立的服务,也可以让 全局事务发起方 充当 TM 的角色, TM 独立出来是为了成为公
用组件,是为了考虑系统结构和软件复用。
TM 在发起全局事务时生成全局事务记录,全局事务 ID 贯穿整个分布式事务调用链条,用来记录事务上下文,
追踪和记录状态,由于 Confirm cancel 失败需进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求
多少次,其结果都相同。
 
4.2.TCC 解决方案
目前市面上的 TCC 框架众多比如下面这几种:
(以下数据采集日为 2019 07 11 日)
 
 
上一节所讲的 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 元给 B A B 账户在不同的服务。
方案 1
账户 A
 
账户 B
 
方案 1 说明:
1 )账户 A ,这里的余额就是所谓的业务资源,按照前面提到的原则,在第一阶段需要检查并预留业务资源,因此,
我们在扣钱 TCC 资源的 Try 接口里先检查 A 账户余额是否足够,如果足够则扣除 30 元。 Confirm 接口表示正式
提交,由于业务资源已经在 Try 接口里扣除掉了,那么在第二阶段的 Confirm 接口里可以什么都不用做。 Cancel
接口的执行表示整个事务回滚,账户 A 回滚则需要把 Try 接口里扣除掉的 30 元还给账户。
2 )账号 B ,在第一阶段 Try 接口里实现给账户 B 加钱, Cancel 接口的执行表示整个事务回滚,账户 B 回滚则需要把
Try 接口里加的 30 元再减去。
方案 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
 
 
账户 B
 
 
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值