【分布式事务】

news2024/11/23 12:57:46

文章目录

  • 解决分布式事务的思路
  • seata四种模式
    • 1. XA模式
    • 2. AT模式
      • AT模式与XA模式的区别是什么?
      • 脏写问题
    • 3. TCC模式
      • 事务悬挂和空回滚
    • 4. SAGA模式
  • 四种模式对比
  • 口述AT模式与TCC模式
  • 高可用

什么是分布式事务?

分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如:

  • 跨数据源的分布式事务
  • 跨服务的分布式事务
  • 综合情况

例如电商行业中比较常见的下单付款案例,包括下面几个行为:

  • 创建新订单
  • 扣减商品库存
  • 从用户账户余额扣除金额

在这里插入图片描述
订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个本地事务,可以保证ACID原则。

但是当我们把三件事情看做一个"业务",要满足保证“业务”的原子性,要么所有操作全部成功,要么全部失败,不允许出现部分成功部分失败的现象,这就是分布式系统下的事务了。

此时ACID难以满足,这是分布式事务要解决的问题


CAP理论
Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致
Availability (可用性):用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。
Partition(分区)**:因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区

解决分布式事务的思路

分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论,有两种解决思路:

  • AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致
  • CP模式:各个子事务执行后互相等待同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。

seata事务管理

Seata事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

在这里插入图片描述

微服务集成seata

  1. 引入依赖
    在这里插入图片描述
  2. 配置TC地址
    在这里插入图片描述

seata四种模式

Seata提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • SAGA模式:长事务模式,有业务侵入

1. XA模式

Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:
在这里插入图片描述

XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则。
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. AT模式

AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。
在这里插入图片描述

AT模式下执行流程

一阶段:

1)TM发起并注册全局事务到TC

2)TM调用分支事务

3)分支事务准备执行业务SQL

4)RM拦截业务SQL,根据where条件查询原始数据,形成快照。

{
    "id": 1, "money": 100
}

5)RM执行业务SQL,提交本地事务,释放数据库锁。此时 money = 90

6)RM报告本地事务状态给TC

二阶段:

1)TM通知TC事务结束

2)TC检查分支事务状态

​ a)如果都成功,则立即删除快照

​ b)如果有分支事务失败,需要回滚。读取快照数据({"id": 1, "money": 100}),将快照恢复到数据库。此时数据库再次恢复为100

AT模式与XA模式的区别是什么?

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

脏写问题

口述:因为AT模式是两阶段的事务模型,在第一个阶段分支事务执行之前首先会记录数据快照,然后执行对应sql,然后就释放了数据库锁,那么在他释放后这一时刻,另一个事务就能拿到数据库锁然后对同一个数据又进行了修改,那么第一个事务在第二阶段回滚恢复快照时,就会覆盖另一个事务的修改。

在多线程并发访问AT模式的分布式事务时,有可能出现脏写问题,如图:
在这里插入图片描述

解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。
在这里插入图片描述
AT模式的优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好
  • 利用全局锁实现读写隔离
  • 没有代码侵入,框架自动完成回滚和提交

AT模式的缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比XA模式要好很多

3. TCC模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;
  • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
  • Cancel:预留资源释放,可以理解为try的反向操作。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
TCC的优点是什么?

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点是什么?

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理

事务悬挂和空回滚

空回滚
当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚
在这里插入图片描述
业务悬挂
对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是业务悬挂

执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂


实现TCC模式案例
解决空回滚和业务悬挂问题,必须要记录当前事务状态,是在try、还是cancel?

  1. 这里我们定义一张冻结表:
    在这里插入图片描述
    其中:
  • xid:是全局事务id
  • freeze_money:用来记录用户冻结金额
  • state:用来记录事务状

那此时,我们的业务开怎么做呢?

  • Try业务:
    • 记录冻结金额和事务状态(state=0)到account_freeze表
    • 扣减account表可用金额
  • Confirm业务
    • 根据xid删除account_freeze表的冻结记录
  • Cancel业务
    • 修改account_freeze表,冻结金额为0,state为2
    • 修改account表,恢复可用金额
  • 如何判断是否空回滚
    • cancel业务中,根据xid查询account_freeze,如果为null则说明try还没做,需要空回滚
  • 如何避免业务悬挂
    • try业务中,根据xid查询account_freeze ,如果已经存在则证明Cancel已经执行,拒绝执行try业务
  1. 声明TCC接口
    TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明,
    我们在account-service项目中的cn.itcast.account.service包中新建一个接口,声明TCC三个接口:
package cn.itcast.account.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

@LocalTCC
public interface AccountTCCService {

    @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money")int money);

    boolean confirm(BusinessActionContext ctx);

    boolean cancel(BusinessActionContext ctx);
}
  1. 编写实体类
    在account-service服务中的cn.itcast.account.service.impl包下新建一个类,实现TCC业务:
package cn.itcast.account.service.impl;

import cn.itcast.account.entity.AccountFreeze;
import cn.itcast.account.mapper.AccountFreezeMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper freezeMapper;

    @Override
    @Transactional
    public void deduct(String userId, int money) {
        // 0.获取事务id
        String xid = RootContext.getXID();
        // 避免特务悬挂,判断freeze表中是否有冻结记录,如果有一定是CANDEL执行过了,拒绝执行try
        AccountFreezeMapper oldFreeze = freezeMapper.selectById(xid);
        if (oldFreeze != null){
            return;
        }
        // 1.扣减可用余额
        accountMapper.deduct(userId, money);
        // 2.记录冻结金额,事务状态
        AccountFreeze freeze = new AccountFreeze();
        freeze.setUserId(userId);
        freeze.setFreezeMoney(money);
        freeze.setState(AccountFreeze.State.TRY);
        freeze.setXid(xid);
        freezeMapper.insert(freeze);
    }

    @Override
    public boolean confirm(BusinessActionContext ctx) {
        // 1.获取事务id
        String xid = ctx.getXid();
        // 2.根据id删除冻结记录
        int count = freezeMapper.deleteById(xid);
        return count == 1;
    }

    @Override
    public boolean cancel(BusinessActionContext ctx) {
        // 0.查询冻结记录
        String xid = ctx.getXid();
        String userId = ctx.getActionContext(userId).toString();
        AccountFreeze freeze = freezeMapper.selectById(xid);
        
       	//空回滚的判断,判断freeze是否为null,为null证明还没有try,需要空回滚
        if (freeze == null){
            //为null证明还没有try,需要空回滚
            freeze = new AccountFreeze();
            freeze.setUserId(userId);
            freeze.setFreezeMoney(0);
            freeze.setState(AccountFreeze.State.CANCEL);
            freeze.setXid(xid);
            freezeMapper.insert(freeze);
        }
        //判断幂等性
        if (freeze.getState() == AccountFreeze.State.CANCEL){
            // 已经处理过一次CANCEL了,无需重复处理
            return true;
        }

        // 1.恢复可用余额
        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
        // 2.将冻结金额清零,状态改为CANCEL
        freeze.setFreezeMoney(0);
        freeze.setState(AccountFreeze.State.CANCEL);
        int count = freezeMapper.updateById(freeze);
        return count == 1;
    }
}

4. SAGA模式

分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
在这里插入图片描述

四种模式对比

在这里插入图片描述

口述AT模式与TCC模式

AT模式与TCC模式都是二阶段的分布式事务模型,每个分支事务在第一阶段它们执行完后都可以立即提交或回滚,当=当然有的分支事务能成功有的就回滚了,当TC(事务调度者)来对这些分支事务做统计,看看最终是将这些事务提交还是回滚,AT模式下的话它是基于数据快照形式的,在操作数据库之前会保存数据库修改前的数据快照,当需要回滚时恢复这些快照就可以,AT下的话还使用了全局锁,来避免第一阶段与第二阶段这个时间内的脏写问题; TCC模式的话其实核心就是三个接口Try、confirm、cancel,它在第一阶段是会进行try操作就是资源的预留的,就是吧对应想操作的数据记录到一张表中,
第二个阶段如果要是提交的话就是执行confirm里面的代码,它直接删除我们表中的数据就可以,如果要回滚的话其实就是执行cancel这个方法的代码,其实类似于执行try的反向操作。这里面的话需要注意空回滚与业务悬挂,就是在try与cancel中来进行具体的判断。

高可用

搭建TC服务集群非常简单,启动多个TC服务,注册到nacos即可。
但集群并不能确保100%安全,万一集群所在机房故障怎么办?所以如果要求较高,一般都会做异地多机房容灾。
在这里插入图片描述
在nacos配置中心配置(配置热更新):
在这里插入图片描述

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

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

相关文章

Arduino ESP32/ESP8266 +ST7735 1.8“tft中秋小时钟

Arduino ESP32 ST7735 1.8"tft中秋小时钟 🌼原作者B站视频: ESP32中秋小时钟,表盘自动切换,代码开源,原图可下载(案例应用) 🎞tft ST7735 128160 1.8" 显示效果:(由于原作…

【vue3】wacth监听,监听ref定义的数据,监听reactive定义的数据,详解踩坑点

假期第二篇,对于基础的知识点,我感觉自己还是很薄弱的。 趁着假期,再去复习一遍 之前已经记录了一篇【vue3基础知识点-computed和watch】 今天在学习的过程中发现,之前记录的这一篇果然是很基础的,很多东西都讲的不够…

gcc中-I(大写的i)参数的作用

《gcc -I -L -l区别》是我参考的一篇博客。 gcc中-I参数可以帮助找到头文件的目录,比如在当前目录下有一个名为includeTestCom.c的c文件和名为includeCom的目录。 includeTestCom.c里边的内容如下: #include "good.h" int main(){printf(&q…

自己动手写编译器:实现命令行模块

在前面一系列章节中,我们完成了词法解析的各种算法。包括解析正则表达式字符串,构建 NFA 状态就,从 NFA 转换为 DFA 状态机,最后实现状态机最小化,接下来我们注重词法解析模块的工程化实现,也就是我们将所有…

CCF-CSP真题《202309-1 坐标变换(其一)》思路+python,c++,java满分题解

想查看其他题的真题及题解的同学可以前往查看:CCF-CSP真题附题解大全 试题编号:202309-1试题名称:坐标变换(其一)时间限制:1.0s内存限制:512.0MB问题描述: 问题描述 对于平面直角坐标…

11链表-迭代与递归

目录 LeetCode之路——206. 反转链表 分析: 解法一:迭代 解法二:递归 LeetCode之路——206. 反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head […

git你学“废”了吗?——git撤销操作指令详解

git你学“废”了吗?——git撤销操作指令详解😎 前言🙌撤销的本质撤销修改情况一:撤销工作区的修改方式一:方式二:演示截图: 撤销修改情况二:撤销暂存区和工作区的修改操作截图&#…

【Java 进阶篇】JDBC DriverManager 详解

JDBC(Java Database Connectivity)是 Java 标准库中用于与数据库进行交互的 API。它允许 Java 应用程序连接到各种不同的数据库管理系统(DBMS),执行 SQL 查询和更新操作,以及处理数据库事务。在 JDBC 中&am…

链表经典面试题(一)

面试题 1.反转链表的题目2.反转链表的图文分析3.反转链表的代码实现 1.反转链表的题目 2.反转链表的图文分析 我们在实现反转链表的时候,是将后面的元素变前面,前面的元素变后面,那么我们是否可以理解为,用头插法的思想来完成反转链表呢&…

力扣:116. 填充每个节点的下一个右侧节点指针(Python3)

题目: 给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针,让这个指针指向其下一个右侧…

计组--总线

一、概念 总线是一组能为多个部件分时共享的公共信息传送线路。 共享是指总线上可以挂接多个部件,各个部件之间互相交换的信息都可以通过这组线路分时共享。 分时是指同一时刻只允许有一个部件向总线发送信息,如果系统中有多个部件,则它们…

qt常用控件1

QLabel QLabel用于显示文本或图像。不提供用户交互功能。标签的视觉外观可以通过多种方式进行配置,并且可用于为另一个小组件指定焦点助记键。 常用API介绍: 获取对应的文本信息: 设置对其方式: 设置能否进行换行 获取及设置标…

mysql面试题9:MySQL中的SQL常见的查询语句有哪些?有哪些对SQL语句优化的方法?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:MySQL中的SQL常见的查询语句有哪些? 常见的SQL查询语句包括: SELECT:用于从一个或多个表中获取数据。 FROM:指定要查询的表名或视图名。 WHER…

ssh爆破分析

1. 2.日志分析 1.系统账号信息 2.确认攻击情况 3.管理员登录情况 4.处理措施

网络基础入门(认识网络 网络传输 概念举例详解)

本篇文章主要是对网络初学的概念进行解释,可以让你对网络有一个大概整体的认知。 文章目录 一、简单认识网络 1、1 什么是网络 1、2 网络分类 二、网络模型 2、1OSI七层模型 2、1、1 简单认识协议 2、1、2 OSI七层模型解释 2、2 TCP/IP五层(或四层)模型 三、网络传…

【生物信息学】计算图网络中节点的中心性指标:聚集系数、介数中心性、度中心性

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 3. IDE 三、实验内容 0. 导入必要的工具 1. 生成邻接矩阵simulate_G: 2. 计算节点的聚集系数 CC(G): 3.计算节点的介数中心性 BC(G) 4. 计算节点的度中心性 DC(G) 5. 综合centrality(G) 6. 代…

《 新手》web前端(axios)后端(java-springboot)对接简解

文章目录 <font color red>1.何为前后端对接?2.对接中关于http的关键点2.1. 请求方法2.2. 请求参数设置简解&#xff1a; 3.对接中的跨域(CROS)问题**为什么后端处理跨域尽量在业务之前进行&#xff1f;**3.总结 1.何为前后端对接? “前后端对接” 是指前端和后端两个…

腾讯云中使用ubuntu安装属于自己的overleaf

在自己的云服务器上安装overleaf的需求是从写论文开始的&#xff0c;总担心自己的论文放在一个网站上被泄露&#xff0c;所以想要在自己的服务器上安装自己的overleaf&#xff0c;正好手边有一个云服务器&#xff0c;现在开始。 配置腾讯云 因为使用overleaf的优势就是在不同…

【小沐学前端】Node.js实现基于Protobuf协议的UDP通信(UDP/TCP)

文章目录 1、简介1.1 node1.2 Protobuf 2、下载和安装2.1 node2.2 Protobuf2.2.1 安装2.2.2 工具 3、node 代码示例3.1 HTTP3.2 UDP单播3.4 UDP广播 4、Protobuf 代码示例4.1 例子: awesome.proto4.1.1 加载.proto文件方式4.1.2 加载.json文件方式4.1.3 加载.js文件方式 4.2 例…

【知识梳理】多级页表的原理分析【地址形成过程】【扩充思考】

多级页表的地址形成过程 首先每个进程中都至少有一个页表&#xff08;段页式可以有多个页表&#xff09;&#xff0c;都有一个页表基地址寄存器&#xff08;PTBR&#xff09;&#xff0c;以下针对三级页表进行分析。 level1&#xff1a;PTBR代表的是一级页表的基地址&#xf…