分布式事务解决方案——TCC

news2024/11/25 9:34:54

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。

1、Try 阶段是做业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的Confirm一起才能真正构成一个完整的业务逻辑。

2、Confirm 阶段是做确认提交,Try阶段所有分支事务执行成功后开始执行Confirm。通常情况下,TCC认为Confirm阶段是不会出错的,若Confirm阶段真的出错了,则重试或人工处理。

3、Cancel 阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常情况下,TCC认为Cancel阶段也是一定成功的,若Cancel阶段真的出错了,则重试或人工处理。

TM(事务管理器)首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作;若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。

所有try都成功

有一个try失败

特别提醒

  1. 所有分支事务的try阶段执行成功,则会执行confirm阶段。TCC认为confirm阶段一定会执行成功,如果confirm执行失败,则会重试或者人工处理错误。

  1. 任何一个分支事务的try阶段执行失败,则会执行cancel阶段。TCC认为cancel阶段一定会执行成功,如果cancel执行失败,则会重试或者人工处理错误。

TCC需要注意三种异常处理分别是:空回滚、幂等、悬挂。

空回滚

事务管理器调用服务的try操作,可能会出现因为丢包而导致的网络超时,导致应用的try阶段没执行,事务管理器认为执行try超时,会触发cancel操作。这就导致cancel比try先执行。

悬挂

1、事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因网络拥堵而导致的超时。

2、此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 执行正常。

3、在此之后,拥堵在网络上的一阶段 Try 数据包被 TCC 服务收到,出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况。

4、此 TCC 服务在执行晚到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel ,造成 TCC 服务悬挂。

幂等

try、confirm、cancel都会被重复调用,需要做幂等处理。

处理空回滚、悬挂、幂等

可以使用日志表,来解决 空回滚、悬挂、幂等。

新建3张表

try阶段日志表local_try_log

字段

注释

tx_no

全局事务id

create_time

创建时间

confirm阶段日志表local_confirm_log

字段

注释

tx_no

全局事务id

create_time

创建时间

cancel阶段日志表local_cancel_log

字段

注释

tx_no

全局事务id

create_time

创建时间

例子

从银行1转账10元给银行2,使用TCC方案

方案1

银行1

try:
    幂等校验,查找try日志(全局事务id是主键)
    悬挂处理,查找confirm、cancel日志(全局事务id是主键)
    检查余额是否够10元
    锁定10元
    插入try日志(全局事务id是主键)
confirm:
    幂等校验,查找confirm日志(全局事务id是主键)
    扣减10元
    删除锁定10元
    插入confirm日志(全局事务id是主键)
cancel:
    cancel幂等校验,查找cancel日志(全局事务id是主键)
    空回滚处理,查找try日志(全局事务id是主键)
    增加余额10元(回滚)
    删除锁定10元
    插入cancel日志(全局事务id是主键)

银行2

try:
    幂等校验,查找try日志(全局事务id是主键)
    悬挂处理,查找confirm、cancel日志(全局事务id是主键)
    插入待激活10元
    插入try日志(全局事务id是主键)
confirm:
    幂等校验,查找confirm日志(全局事务id是主键)
    正式增加30元
    删除待激活10元
    插入confirm日志(全局事务id是主键)
cancel:
    空

由于业务很简单,上面的流程还可以取消锁定,解锁的操作,直接在银行1的try中扣减10元,流程如下。

方案2

银行1

try:
    幂等校验,查找try日志(全局事务id是主键)
    悬挂处理,查找confirm、cancel日志(全局事务id是主键)
    检查余额是否够10元
    扣减10元
    插入try日志(全局事务id是主键)
confirm:
    空
cancel:
    cancel幂等校验,查找cancel日志(全局事务id是主键)
    空回滚处理,查找try日志(全局事务id是主键)
    增加余额10元(回滚)
    插入cancel日志(全局事务id是主键)

银行2

try:
    空
confirm:
    幂等校验,查找confirm日志(全局事务id是主键)
    正式增加30元
    插入confirm日志(全局事务id是主键)
cancel:
    空

Hmily实现方案2的代码

服务1

@Service
@Slf4j
public class Bank1ServiceImpl implements Bank1Service {

    @Autowired
    AccountInfoDao accountInfoDao;
    @Autowired
    HmilyLogDao hmilyLogDao;

    @Autowired
    Bank2Client bank2Client;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Hmily(confirmMethod = "confirm", cancelMethod = "cancel")
    public void updateAccountBalance(String msg, Double amount) {
        // 全局事务id
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank1 try 开始,transId={}", transId);

        // 幂等判断
        int existTry = hmilyLogDao.isExistTry(transId);
        // 通故全局事务id查找到try日志,表明已经只执行过try
        if (existTry > 0) {
            log.info("已经执行过try,无需重复执行try,transId={}", transId);
            return;
        }

        // 悬挂处理
        int existConfirm = hmilyLogDao.isExistConfirm(transId);
        int existCancel = hmilyLogDao.isExistCancel(transId);
        // 通故全局事务id查找到confirm、cancel日志,表明已经只执行过confirm、cancel
        if (existConfirm > 0 || existCancel > 0) {
            log.info("confirm,cancel有一个已经执行过,try不能再次执行,transId={}", transId);
            return;
        }

        // 制造空回滚
        if (StringUtils.equals("制造空回滚", msg)) {
            throw new RuntimeException("try方法没修改数据库就抛出异常,cancel方法会执行,形成空回滚,transId=" + transId);
        }

        // blank1减金额
        accountInfoDao.subtractAccountBalance("1", amount);

        // 添加try日志记录,try日志和扣减余额在同一个本地事务中,要么都成功,要么都失败
        // 日志的组件id必须是全局事务id,如果同一个事物重复调用try,到这一步会报主键重复
        hmilyLogDao.addTry(transId);

        // 远程调用
        Boolean result = bank2Client.transfer(msg, amount);
        if (!result) {
            throw new RuntimeException("调用bank2失败");
        }

        // bank1调用bank2成功后,发生异常,模拟回滚
        if (StringUtils.equals("bank1调用bank2成功后,发生异常,模拟回滚", msg)) {
            throw new RuntimeException("bank1调用bank2成功后,发生异常,模拟回滚,transId=" + transId);
        }
    }

    public void confirm(String accountNo, Double amount) {
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank1 confirm 开始执行,transId={}", transId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void cancel(String msg, Double amount) {
        // 全局事务id
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank1 cancel 开始执行,transId={}", transId);

        // 幂等判断
        int existCancel = hmilyLogDao.isExistCancel(transId);
        if (existCancel > 0) {
            log.info("cancel已经执行过,无需重复执行,transId={}", transId);
            return;
        }

        // 处理空回滚
        int existTry = hmilyLogDao.isExistTry(transId);
        if (existTry == 0) {
            log.info("try未执行过,不能执行cancel,transId={}", transId);
            return;
        }

        // bank1回滚,加钱
        accountInfoDao.addAccountBalance(msg, amount);
        // 添加日志
        hmilyLogDao.addCancel(transId);
    }

}
@Service
@Slf4j
public class Bank2ServiceImpl implements Bank2Service {

    @Autowired
    AccountInfoDao accountInfoDao;

    @Autowired
    HmilyLogDao hmilyLogDao;

    @Override
    @Hmily(confirmMethod = "confirm", cancelMethod = "cancel")
    public void updateAccountBalance(String msg, Double amount) {
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank2 try 开始执行,transId:{}",transId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void confirm(String msg, Double amount) {
        // 全局事务id
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank2 confirm 开始执行,transId:{}",transId);

        int existConfirm = hmilyLogDao.isExistConfirm(transId);
        if (existConfirm > 0) {
            log.info("bank2 confirm 已经执行过,无需再次执行,transId", transId);
            return;
        }

        // bank2加钱
        accountInfoDao.addAccountBalance("2", amount);
        // 添加confirm日志
        hmilyLogDao.addConfirm(transId);

        // bank2 confirm,抛出异常,会重试
        if (StringUtils.equals("confirm抛出异常会重试", msg)) {
            throw new RuntimeException("confirm抛出异常会重试,transId=" + transId);
        }
    }

    public void cancel(String msg, Double amount) {
        String transId = HmilyTransactionContextLocal.getInstance().get().getTransId();
        log.info("bank2 cancel 开始执行,transId:{}",transId);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/346234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win11系统如何安装Ubuntu20.04(WSL版本)并安装docker

终于还是下定决心去换电脑了……这次采用轻量级的WSL,发现虽然没有占内存的GUI界面,但是编码和阅读文档还是非常nice的 1、首先开启Win11的虚拟机服务 2、下载你期望的Ubuntu服务器(这里以20.04为例) 安装成功后,发现…

发布的软文如何提高阅读量?

软文文章是一种带有宣传性质的文章,旨在宣传某个产品、品牌或者公司。然而,要想有效地宣传,就必须要让读者真正的阅读并了解软文中的内容。因此,提高软文文章阅读量是非常重要的。下面是一些有助于提高软文文章阅读量的方法&#…

简单记录简单记录

目录1.注册Gmail2.注册ChatGPT3.验证“真人”使用4.开始使用1.注册Gmail 第一步先注册一个谷歌邮箱,你也可以使用微软账号,大部分人选择使用gmail。 申请谷歌邮箱 选择个人用途创建账号即可。 📌温馨提示: 你直接使用guo内的网…

react路由详解

在学习react路由之前,我们肯定需要安装路由。大家先运行如下命令安装路由。安装之后随我一起探索react路由。 安装 版本v6 npm i react-router-dom -S 页面准备 创建两个文件夹 pages和 router pages文件夹里面放的是页面 router文件夹里面是进行路由配置 路由…

【MySQL】聚合函数和GROUP BY

文章目录1、聚合函数2、GROUP BY3、HAVING4、SELECT的执行过程1、聚合函数 聚合函数作用于一组数据,并对一组数据返回一个值。聚合函数类型:AVG()、SUM()、MAX()、MIN()、COUNT()。不能在 WHERE 子句中使用聚合函数。 SELECT AVG(salary), MAX(salary)…

从0到1一步一步玩转openEuler--14 openEuler DNF(YUM)配置管理

文章目录14.1 DNF配置文件14.1.1 配置main部分14.1.2 配置repository部分14.1.3 显示当前配置14.2 创建本地软件源仓库14.3 添加、启用和禁用软件源14.3.1 添加软件源14.3.2 禁用软件源14.3.3 启用软件源DNF是一款Linux软件包管理工具,用于管理RPM软件包。DNF可以查…

论文笔记: Monocular Depth Estimation: a Review of the 2022 State of the Art

中文标题:单目深度估计:回顾2022年最先进技术 本文对比了物种最近的基于深度学习的单目深度估计方法: GPLDepth(2022)[15]: Global-Local Path Networks for Monocular Depth Estimation with Vertical CutDepthAdabins(2021)[1]: Adabins:…

操作系统:文件系统的实现

一、文件系统结构 磁盘的逻辑单元为块,内存和磁盘之间的I/O传输以块为单位执行。 磁盘的特点 1可以原地重写,可以从磁盘上读一块儿,修改该块,并将它写回到原来的位置可以直接访问磁盘上的任意一块。因此,可以方便地…

浅析SCSI协议(2)命令模型

SCSI命令模型 SCSI协议对链路传输没有进行严格的限制,但约束了SCSI的命令模型。协议允许使用SAS、FC以及以太网等不同的链路实现,但对所有的SCSI传输层实现而言,都必须遵循统一的SCSI命令模型。SCSI命令模型约束了SCSI命令执行过程、命令请求…

《底层逻辑:看清这个世界的底牌》读后感

书名《底层逻辑:看清这个世界的底牌》作者刘润简介如果只教给你各行各业的“干货”(方法论),那只是“授人以鱼”,一旦环境出现任何变化,“干货”就不再适用。但如果教给你的是底层逻辑,那就是“…

初入测试如何编写测试用例?从3个方面带你写一个合格的测试用例

前言 作为一个测试新人,刚开始接触测试,对于怎么写测试用例很头疼,无法接触需求,只能根据站在用户的角度去做测试,但是这样情况会导致不能全方位的测试APP,这种情况就需要一份测试用例了,但是不…

亿级高并发电商项目-- 实战篇 --万达商城项目 七(品牌模块、商品类型模块等开发)

专栏:高并发---分布式 在管理商品时,除了商品名、价格、商品介绍等基本参数外。还需 要给商品添加品牌、商品类型、商品规格等参数。比如Iphone13的 品牌是苹果。商品类型属于手机通讯>手机>手机。规格有机身颜色: 星光色、版本:128G。品牌、商品…

暗网与深网:5 个主要区别

在互联网上,深网不会被网络爬虫索引,而暗网是故意隐藏的。 文章目录前言一、暗网与深网二、什么是暗网?什么是深网?暗网和深网之间的 5 个主要区别1. 范围和操作暗网深网2. 尺寸暗网:深网:3. 访问暗网深网4…

数据的分组聚合

1:分组 t.groupby #coding:utf-8 import pandas as pd import numpy as np file_path./starbucks_store_worldwide.csv dfpd.read_csv(file_path) #print(df.head(1)) #print(df.info()) groupeddf.groupby(byCountry) print(grouped) #DataFrameGroupBy #可以遍历…

1.2配置OSPF包文分析和验证

1.2.2实验2:配置OSPF包文分析和验证 [1] 实验目的通过抓包分析OSPF的包文实现OSPF区域认证的配置实验拓扑实验拓扑图如图1-3所示。 图1-3 配置OSPF包文分析和验证 实验步骤 IP地址的配置、运行OSPF的步骤与实验1相同,此处略。[2] 在R1的g0/0/0抓包

Redis 集群搭建及集群管理工具

目录一、简介二、架构图三、搭建集群3.1、下载3.2、编译安装3.3、配置文件修改3.4、创建集群四、集群管理工具redis-cli4.1、查看集群信息4.2、检查集群4.3、修复槽错误4.4、重分片4.5、负载均衡4.6、添加节点4.7、删除节点4.8、设置超时4.9、导入数据4.10、重建集群一、简介 本…

【JavaWeb】网络编程概念 + Socket套接字 + UDP/TCP编程

目录 网络编程基础概念 发送端与接受端 请求与响应 客户端与服务器 常见的客户端服务器模型 Socket套接字 回显(echo)程序 UDP版的回显程序 服务器代码 客户端代码 结果 TCP版的回显程序 服务器代码 客户端代码 结果 网络编程基础概念 网络编程,指网…

2.5|物联网应用系统设计|复习提纲|提问背诵

基础概念总结掌握Linux常用的基本命令功能、语法结构,用法等。具体命令参考实验指导书、相关PPT等资料内容。什么是操作系统(OS)?操作系统是用以控制和管理计算机系统资源,方便用户使用的程序和数据结构的集合。在所有…

零基础学习Python的一点建议

Python语言的火爆程度,真的是超过了任何一门计算机语言,当然火爆程度里面含有赶上了人工智能这个领域的风口,但是大部分的原因是Python易学,语法对小白非常友好,总结一句话,Python语言能做很多事情&#xf…

亿级高并发电商项目-- 实战篇 --万达商城项目 六(编写角色管理、用户权限(Spring Security认证授权)、管理员管理等模块)

专栏:高并发---前后端分布式 👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者 📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信…