首页 > 其他分享 >TCC事务模式使用

TCC事务模式使用

时间:2024-01-10 14:34:37浏览次数:36  
标签:回滚 dto 事务 模式 TRY actionContext TCC

整体机制

TCC模式采用的也是两阶段提交的模型,区别于AT和XA模式,TCC模式的两阶段需要自定义实现,不依赖于数据库的事务模型和协议。
机制示例图

工作机制

TCC模式客户端使用时需要分try、commit、cancel三个部分:

  • try:检查预留资源
  • commit:执行真正业务的提交
  • Cancel:预留资源的释放

工作机制示例图

异常场景控制

在 TCC 模型执行的过程中,还可能会出现各种异常,其中最为常见的有空回滚、幂等、悬挂等。

空回滚

出现场景:
空回滚指的是在一个分布式事务中,在没有调用参与方的 Try 方法的情况下,TM 驱动二阶段回滚调用了参与方的 Cancel 方法;一般出现原因是执行Try阶段时,try执行机器发生网络异常或者宕机。
处理方式:
新增一个 TCC 事务控制表,包含事务的 XID 和 BranchID 信息,在 Try 方法执行时插入一条记录,表示一阶段执行了,执行 Cancel 方法时读取这条记录,如果记录不存在,说明 Try 方法没有执行。

幂等

出现场景:
分支参与者在执行完commit后出现网络故障或者宕机,导致TC没有收到commit的返回结果,TC 会重复发起调用,直到二阶段执行结果成功。
处理方式:
在 TCC 事务控制表中增加一个记录状态的字段 status,该字段有 3 个值,分别为:
-tried:1
-committed:2
-rollbacked:3
二阶段 Confirm/Cancel 方法执行后,将状态改为 committed 或 rollbacked 状态。当重复调用二阶段 Confirm/Cancel 方法时,判断事务状态即可解决幂等问题。

防悬挂

出现场景:
分支参与者在执行一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务有超时限制,执行 Try 方法超时后,TM 决议全局回滚,回滚完成后如果此时 RPC 请求才到达参与者 A,执行 Try 方法进行资源预留,从而造成悬挂。
处理方式:
当执行二阶段 Cancel 方法时,如果发现 TCC 事务控制表没有相关记录,说明二阶段 Cancel 方法优先一阶段 Try 方法执行,因此在事务控制表插入一条回滚记录;在执行一阶段Try时先读取事务控制表,若查询到回滚记录,说明出现悬挂,则停止执行业务直接返回。

集成过程

Seata客户端集成
事务执行客户端基本搭建过程与其他模式一致,全局事务发起方添加@GlobalTransactional注解即可,事务接收方需要实现自定义TTC(Try-Commit-Cancel),过程如下:

  • TCC定义
  1. 业务层接口上添加@LocalTCC注解
  2. 指定执行事务一阶段的方法,在方法上添加@TwoPhaseBusinessAction,同时指定二阶段的commit方法和cancel方法。
    参考代码
@LocalTCC
public interface FundManageService {

    List<FundInfoEntity> queryFundInfo(FundInfoDto dto);

    FundInfoEntity queryFundInfoDetail(String code);

    FundInfoEntity queryFundInfoDetailForGlobal(String code);

    void addFundInfo(FundInfoDto dto);

    /**
     * 编辑数据
     * @param dto
     */
    void editFundInfo(FundInfoDto dto);

    /**
     * 编辑数据
     * @param actionContext
     * @param dto
     */
    @TwoPhaseBusinessAction(name = "EditFundInfoOne", commitMethod = "commit", rollbackMethod = "rollback")
    void editFundInfo(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "tx_param") FundInfoDto dto);

    /**
     * 减少基金池份额
     * @param dto
     */
    void reduceFundShr(FundInfoDto dto);


    boolean commit(BusinessActionContext actionContext);

    boolean rollback(BusinessActionContext actionContext);
  • TCC实现
    1.实现一阶段try,实现内容包含防悬挂、预留业务资源、添加全局事务日志
@Override
    public void editFundInfo(BusinessActionContext actionContext, FundInfoDto dto) {
        //1. 实现防悬挂
        FundTransactionEntity transactionEntity = fundTransactionMapper.selectTxById(actionContext.getBranchId(), actionContext.getXid());
        if (null != transactionEntity) {
            logger.error("【TRY-TRY】事务已经回滚,出现悬挂,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            return;
        }
        //2. 插入事务控制记录,用于后续处理幂等和空回滚
        addTxLog(dto, actionContext, FundTransactionTypeEnum.TCC_TRY.name(), FundTransactionStateEnum.STATE_TRY.name());
        //3. 冻结资金份额
        FundInfoEntity entity = new FundInfoEntity();
        BeanUtils.copyProperties(dto, entity);
        fundManageMapper.freezeFundShr(entity);
    }
  1. 实现二阶段commit,实现内容包含保证幂等、完成业务提交、修改全局事务状态
@Override
    public boolean commit(BusinessActionContext actionContext) {
        //1. 基于全局事务id和分支事务id获取事务记录
        FundTransactionEntity transactionEntity = fundTransactionMapper.selectTxById(actionContext.getBranchId(), actionContext.getXid());
        //2. 判断是否为空,如果为空,说明一阶段的try没有执行
        if (transactionEntity == null) {
            logger.error("【TRY-COMMIT】获取事务记录失败,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            return true;
        } else if (transactionEntity.getState().equals(FundTransactionStateEnum.STATE_COMMIT.name())) {
            //3. 判断事务是否已经提交,若提交,流程终止,保证幂等性
            logger.error("【TRY-COMMIT】事务记录已经提交,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            return true;
        } else if (transactionEntity.getState().equals(FundTransactionStateEnum.STATE_CANCEL.name())) {
            //4. 判断事务是否已经回滚,若回滚,流程终止
            logger.error("【TRY-COMMIT】事务记录已经回滚,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            return true;
        }

        FundInfoDto dto = actionContext.getActionContext(TX_PARAM, FundInfoDto.class);
        FundInfoEntity entity = new FundInfoEntity();
        BeanUtils.copyProperties(dto, entity);
        //5. 执行业务代码,完成业务
        fundManageMapper.commitFundShr(entity);
        //6. 调整事务日志记录为“提交”
        Integer row = updateTxLog(actionContext, FundTransactionStateEnum.STATE_COMMIT.name());
        Assert.state(row != null && row > 0,"【TRY-COMMIT】事务记录提交失败,事务状态已经发生变化,需要更新数据!");
        return true;
    }

3.实现二阶段cancel,实现内容包含防止空回滚、释放预留资源、修改全局事务状态

@Override
    public boolean rollback(BusinessActionContext actionContext) {
        //1. 先查询事务记录,判断是否为空(空回滚)
        FundTransactionEntity transactionEntity = fundTransactionMapper.selectTxById(actionContext.getBranchId(), actionContext.getXid());
        FundInfoDto dto = actionContext.getActionContext(TX_PARAM, FundInfoDto.class);
        FundInfoEntity entity = new FundInfoEntity();
        if (dto == null) {
            return true;
        }
        BeanUtils.copyProperties(dto, entity);
        //2. 如果为空,插入一条CANCEL类型的事务记录,并终止方案
        if (transactionEntity == null) {
            logger.error("【TRY-ROLLBACK】事务记录出现空回滚,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            this.addTxLog(dto, actionContext, FundTransactionTypeEnum.TCC_TRY.name(), FundTransactionStateEnum.STATE_CANCEL.name());
            //出现空回滚以后,无论是否出现异常,都应该直接结束方法
            return true;
        }

        //3. 如果不为空,判断事务状态是否已经回滚,如果回滚说明重复执行了回滚操作,直接结束方法
        if (transactionEntity.getState().equals(FundTransactionStateEnum.STATE_CANCEL.name())) {
            logger.error("【TRY-ROLLBACK】事务记录重复回滚,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            return true;
        } else if (transactionEntity.getState().equals(FundTransactionStateEnum.STATE_COMMIT.name())) {
            //4. 否则判断是否为已提交,如果是,说明流程异常,结束方法
            logger.error("【TRY-ROLLBACK】事务已经提交,不允许再回滚,流程终止,xid:{}, branchId:{}", actionContext.getXid(), actionContext.getBranchId());
            return true;
        }

        //5. 如果是初始化一阶段完成状态,则回滚修改的业务数据
        fundManageMapper.unFreezeFundShr(entity);

        //6. 变更事务状态为“已回滚”
        Integer row = this.updateTxLog(actionContext, FundTransactionStateEnum.STATE_CANCEL.name());
        Assert.state(row != null && row > 0,"【TRY-ROLLBACK】事务记录提交失败,事务状态已经发生变化,需要更新数据!");
        return true;
    }

特征

  • 优点:TCC过程自定义实现,不依赖于数据库,可以解决各种复杂场景以及非关系型数据库类型的分布式事务问题。
  • 缺点: 实现TCC过程对业务代码渗透的较深,代码编码量比较大,提高了实现难度。

参考文献

深度剖析 Seata TCC 模式
TCC 理论及设计实现指南介绍

标签:回滚,dto,事务,模式,TRY,actionContext,TCC
From: https://www.cnblogs.com/zly1015/p/17956143

相关文章

  • WinDbg调试基础教程-用户模式
    在前面的文章中,介绍了如何使用WinDbg分析蓝屏原因https://www.cnblogs.com/zhaotianff/p/15150244.html 不过那会都是在网上找的资料,东拼西凑出来,并没有系统的去学习WinDbg。最近在学习内核开发这一块的内容,刚好要用到WinDbg,所以这里找资料进行系统的入门一下,做个总结。 ......
  • 《PySpark大数据分析实战》-11.Spark on YARN模式安装Hadoop
    ......
  • 程序员必知!组合模式的实战应用与案例分析
    组合模式是一种设计模式,允许将对象组合成树形结构并像单个对象一样使用它们,这种模式在处理类似公司组织结构这样的树形数据时非常有用,通过组合模式,我们可以将公司和部门视为同一类型的对象,从而以统一的方式处理发送给不同层级的请求或任务,叶节点是没有子节点的对象,而复合节点则包......
  • 程序员必知!代理模式的实战应用与案例分析
    代理模式是在不改变原对象基础上,通过代理对象控制访问并添加额外操作,以销售代表和助理为例,助理作为代理对象,处理邮件、数据等琐碎工作,使销售代表能专注于与客户面对面交流推销,代理模式让原对象功能得以扩展,同时保持其对外接口的透明性。定义代理模式提供了一种在不改变原有对......
  • 《PySpark大数据分析实战》-10.独立集群模式的代码运行
    ......
  • MyBatis—Spring 动态数据源事务的处理
    在一般的Spring应用中,如果底层数据库访问采用的是MyBatis,那么在大多数情况下,只使用一个单独的数据源,Spring的事务管理在大多数情况下都是有效的。然而,在一些复杂的业务场景下,如需要在某一时刻访问不同的数据库,由于Spring对于事务管理实现的方式,可能不能达到预期的效果。本文......
  • 【设计模式】建造者模式——建造者模式在Android SDK源码中的应用
    建造者模式在AndroidSDK源码中也有广泛的应用,本文挑两个典型的类讨论一下:AlertDialog.Builder在Android源码中最常用到的建造者模式非AlertDialog.Builder莫属,代码如下:AlertDialogalertDialog=newAlertDialog.Builder(mContext) .setTitle("系统提示:").setMessage("请......
  • 2023 年精选:每个 DevOps 团队都应该了解的 5 种微服务设计模式
    微服务彻底改变了应用程序开发世界,将大型整体系统分解为更小、更易于管理的组件。这种架构风格的特点是独立、松散耦合的服务,带来了从可扩展性、模块化到更高的灵活性等众多优势。DevOps团队如何最好地利用这种方法来实现最高效率?答案在于理解并有效地采用微服务设计模式。在本文......
  • 「微服务」Saga 模式 如何使用微服务实现业务事务-第二部分
    在上一篇文章中,我们看到了实现分布式事务的一些挑战,以及如何使用Event/Choreography方法实现Saga的模式。在本文中,我们将讨论如何通过使用另一种类型的Saga实现(称为Command或Orchestration)来解决一些问题,如复杂事务或事件的循环依赖性。Saga的命令/编曲序列逻辑在编曲方法中,我们......
  • 数据访问控制的事务处理与一致性保障
    1.背景介绍数据访问控制(DataAccessControl,DAC)是一种基于访问控制列表(AccessControlList,ACL)的安全机制,它允许系统用户在请求访问某个对象时,根据其具有的权限来决定是否允许访问。这种机制在数据库系统、文件系统和网络系统中都有广泛应用。在数据库系统中,事务处理(Transactio......