分布式事务(三)———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
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值