• 分布式事务(三)———TCC 解决方案


    该系列参考:
    https://www.jianshu.com/p/962271bbf4ea
    https://blog.csdn.net/a745233700/article/details/122402303
    https://cloud.tencent.com/developer/article/2048776
    https://icyfenix.cn/

    一、TCC介绍

    TCC 是“Try-Confirm-Cancel”三个单词的缩写
    最核心的思想:就是在应用层将一个完整的事务操作分为三个阶段。在某种程度上讲,TCC 是一种资源,实现了 Try、Confirm、Cancel 三个操作接口。与传统的两阶段提交协议不同的是,TCC 是一种在应用层实现的两阶段提交协议,在 TCC 分布式事务中,对每个业务操作都会分为 Try、Confirm 和 Cancel 三个阶段,每个阶段所关注的重点不同。

    • Try尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好全部需用到的业务资源(保障隔离性)。
    • Confirm确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。Confirm 阶段可能会重复执行,因此本阶段所执行的操作需要具备幂等性。
    • Cancel取消执行阶段,释放 Try 阶段预留的业务资源。Cancel 阶段可能会重复执行,也需要满足幂等性。

    二、TCC执行过程

    (来自凤凰架构
    在这里插入图片描述

    1. 最终用户向 Fenix’s Bookstore 发送交易请求:购买一本价值 100 元的《深入理解 Java 虚拟机》。
    2. 创建事务,生成事务 ID,记录在活动日志中,进入 Try 阶段:
      • 用户服务:检查业务可行性,可行的话,将该用户的 100 元设置为“冻结”状态,通知下一步进入 Confirm 阶段;不可行的话,通知下一步进入 Cancel 阶段。
      • 仓库服务:检查业务可行性,可行的话,将该仓库的 1 本《深入理解 Java 虚拟机》设置为“冻结”状态,通知下一步进入 Confirm 阶段;不可行的话,通知下一步进入 Cancel 阶段。
      • 商家服务:检查业务可行性,不需要冻结资源。
    3. 如果第 2 步所有业务均反馈业务可行,将活动日志中的状态记录为 Confirm,进入 Confirm 阶段:
      • 用户服务:完成业务操作(扣减那被冻结的 100 元)。
      • 仓库服务:完成业务操作(标记那 1 本冻结的书为出库状态,扣减相应库存)。
      • 商家服务:完成业务操作(收款 100 元)。
    4. 第 3 步如果全部完成,事务宣告正常结束,如果第 3 步中任何一方出现异常,不论是业务异常或者网络异常,都将根据活动日志中的记录,重复执行该服务的 Confirm 操作,即进行最大努力交付。
    5. 如果第 2 步有任意一方反馈业务不可行,或任意一方超时,将活动日志的状态记录为 Cancel,进入 Cancel 阶段:
      • 用户服务:取消业务操作(释放被冻结的 100 元)。
      • 仓库服务:取消业务操作(释放被冻结的 1 本书)。
      • 商家服务:取消业务操作(大哭一场后安慰商家谋生不易)。
    6. 第 5 步如果全部完成,事务宣告以失败回滚结束,如果第 5 步中任何一方出现异常,不论是业务异常或者网络异常,都将根据活动日志中的记录,重复执行该服务的 Cancel 操作,即进行最大努力交付。

    二、优缺点

    1. 优点:
      • 整体过程类似2PC,但却是位于代码层面,有较高的灵活性,可以根据需要设计资源锁定粒度,具有很高的性能
      • 有较强的的隔离性
    2. 缺点:
    • 代码侵入性强,开发成本高
    • 非强一致性,属于补偿事务,实现最终一致

    三、存在的三个问题

    1.空回滚

    当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚

    空回滚情况: 上方调用分支按照TCC流程正常执行,此时下方调用分支因为某种原因而阻塞了,由于长时间没有执行,这个分支发生了超时错误,由TM经过2.1步骤发送超时错误,回滚全局事务的指令给TC,TC检查分支状态2.2,发现确实有一只分支超时,发送2.3回滚指令到各分支的RM,由RM执行2.4 cancel操作。 此时对于第一个分支而言,执行cancel没有问题,因为流程正常。但对于第二个分支而言,他并没有执行第一步的try,所以此时第二个分支不能真正的执行cancel,需要执行空回滚,也就是说返回一个正常状态,且不报错。

    解决:需要在cancel之前查看是否有前置的try,如果没有执行try则需要空回滚。

    2.悬挂

    对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂

    在这里插入图片描述

    业务悬挂情况: 假设在上方的基础上,下方分支的阻塞畅通了,此时他执行1.4去锁定资源(try),但整个事务都已经回滚结束了,所以他不会执行第二阶段,但冻结了资源,这种情况应该进行避免。

    解决:需要在try操作之前查看当前分支是否已经回滚过,如果已经回滚过则不能在执行try命令。

    3.幂等性

    Confim、Cancel阶段不论网络原因或者业务异常,都会根据日志记录重复执行操作,所以需要保持接口的幂等性,也就是最多执行一次

    四、Seata-TCC实现

    DEMO详情见 : https://github.com/seata/seata-samples
    Seata文档详情见: https://seata.io/zh-cn/docs/overview/what-is-seata.html

    接口准备:

    1. 接口上使用@LocalTCC注解
    2. Try方法上使用 @TwoPhaseBusinessAction注解, 配置好Try、Confirm、Cancel方法
    3. 需要传递的参数使用 @BusinessActionContextParameter注解配置
    4. Confirm、Cancel方法需要boolean返回值
    5. 实现该接口,编写业务逻辑即可 (事务ID获取方法:String xid = RootContext.getXID()
    @LocalTCC
    public interface AccountTCCService {
    
        @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
        public void deduct(
                @BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money") int money
        );
    
        public boolean confirm(BusinessActionContext context);
    
        public boolean cancel(BusinessActionContext context);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    系列文章

  • 相关阅读:
    Spring AOP(面向切面编程)简介说明
    Gin的请求与响应
    GPU并行计算- 基础知识
    2.3 LINQ
    nginx实现双向认证
    Scala入门到精通(尚硅谷学习笔记)章节八——面向对象
    Linux下安装Redis详细教程 (附安装包)
    嵌入式开发没有激情了,正常吗?
    一文搞懂 == 、equals和hashCode
    基于Matlab实现多个数字水印案例(附上源码+数据集)
  • 原文地址:https://blog.csdn.net/weixin_44102992/article/details/126493034