一文搞懂分布式事务解决方案

前言

在这里插入图片描述

在当今的分布式系统中,分布式事务管理是一个关键挑战。在面对跨多个服务的复杂业务流程时,确保数据一致性和事务的原子性变得至关重要。本文将深入探讨分布式事务的概念、原理、实现方式以及在Java领域的应用。

什么是分布式事务

分布式事务是指涉及多个独立的系统或服务的事务操作。在分布式系统中,由于数据存储在不同的数据库或服务中,并且跨越多个计算节点,因此需要跨系统进行事务协调和管理,以确保事务的原子性、一致性、隔离性和持久性(ACID原则)。

可能语言文字描述的有点抽象,我用几张图片,教你弄懂分布式事务。

传统单体架构

在互联网发展初期,单体架构完全可以满足现有业务需求,所有的业务共用一个数据库,整个业务流程或许只用在一个方法里同一个事务下操作数据库即可。此时做到所有操作要么全部提交 或 要么全部回滚很容易。

@transactional
public void demoService(UserInfo userInfo) {// 1.用户付款userMapper.payMoney(userInfo);// 2.订单增加orderMapper.addItem(userInfo);
}

在这里插入图片描述
可随着业务量的不断增长,单体架构渐渐扛不住巨大的流量,时间久了,各种各样的问题自然而然的也出现了:复杂性高,部署频率低,可靠性差,扩展能力受限,此时就需要对数据库、表做分库分表处理,将应用服务进行拆分。也就产生了用户服务、订单服务、库存服务等,由此带来的问题就是服务与服务之间的独立部署,互相隔离,每个微服务都维护着自己的数据库,服务之间的调用只能通过RPC远程掉调用,此时单体架构的数据库事务就无法做到全局事务的管理。当用户再次进行付款服务的时候,此时不同的服务只能保证各自的数据库事务。所以为了保证整个支付流程的数据一致性,就需要分布式事务了。

分布式部署架构

在这里插入图片描述
了解分布式事务,就不得不提一下分布式事务中比较经典的一些理论知识。

CAP理论

CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)

在理论计算机科学中,CAP 定理指出对于一个分布式系统来说,当设计读写操作时,只能同时满足以下三点中的两个:一致性(Consistency) : 所有节点访问同一份最新的数据副本可用性(Availability): 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。分区容错性(Partition Tolerance) : 分布式系统出现网络分区的时候,仍然能够对外提供服务。

分布式系统中,多个节点之间的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。CAP 目前来说无法都兼备,因此当前微服务策略中要么要么CP,或者AP。

BASE理论

这个时候又有一个理论出现了,那就是 BASE理论 。它是用来对 CAP理论 进行一些补充,

BA(Basically Available):基本可用
S(Soft State):软状态
E(Eventually Consistent):最终一致性
这个理论的核心思想便是:如果我们如法做到强一致性,那么每个应用都应该根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

分布式事务解决方案

所以在分布式背景下,两个服务之间相互调用,使用的是不用的数据库,这种情况下肯定会出现分布式事务的问题。

2PC(两阶段提交)

基于 XA 协议实现的分布式事务,XA 协议中分为两部分:事务管理器和本地资源管理器。

两阶段提交是把整个事务提交分为 prepare 和 commit 两个阶段以支付系统为例,分布式系统中有用户、订单和库存三个服务,如下图:

第一阶段:prepare 阶段
在这里插入图片描述
第二阶段:commit 阶段
在这里插入图片描述
两阶段提交(2PC),对业务侵⼊很小,它最⼤的优势就是对使⽤⽅透明,用户可以像使⽤本地事务⼀样使⽤基于 XA 协议的分布式事务,能够严格保障事务 ACID 特性。

2PC的缺点也是显而易见,它是一个强一致性的同步阻塞协议,事务执⾏过程中需要将所需资源全部锁定,也就是俗称的刚性事务。所以它比较适⽤于执⾏时间确定的短事务,整体性能比较差。

一旦事务协调者宕机或者发生网络抖动,会让参与者一直处于锁定资源的状态或者只有一部分参与者提交成功,导致数据的不一致。因此,在⾼并发性能⾄上的场景中,基于 XA 协议的分布式事务并不是最佳选择。

3PC(三阶段提交)

三段提交(3PC)是二阶段提交(2PC)的一种改进版本 ,为解决两阶段提交协议的阻塞问题,上边提到两段提交,当协调者崩溃时,参与者不能做出最后的选择,就会一直保持阻塞锁定资源。

为了解决两阶段提交的问题,三阶段提交做了改进:

  1. 在协调节点和事务参与者都引入了超时机制。
  2. 第一阶段的 prepare 阶段分成了两步,canCommit 和 preCommit。

第一阶段:canCommit
在这里插入图片描述
第二阶段:preCommit
在这里插入图片描述
第三阶段:commit 阶段
在这里插入图片描述
虽然 3PC 用超时机制,解决了协调者故障后参与者的阻塞问题,但与此同时却多了一次网络通信,性能上反而变得更差,如果第三阶段发出 rollback 请求,有的节点没有收到,那没有收到的节点会在超时之后进行提交,造成数据不一致。

TCC (Try Confirm Cancel) 事务补偿

TCC它是属于补偿型分布式事务。它的核心思想是 针对每个操作,都要注册一个与其对应的确认和补偿操作。TCC 实现分布式事务一共有三个步骤:分别指 Try、Confirm、Cancel ,一个业务操作要对应的写这三个方法。

TCC 不存在资源阻塞的问题,因为每个方法都直接进行事务的提交,一旦出现异常通过则 Cancel 来进行回滚补偿,这也就是常说的补偿性事务。

TCC是基于业务层面做的分布式事务,最终目的是达到数据最终一致性,是一种柔性事务。

下面以一个例子来说明三个阶段需要做的事:比如现在有两个数据库,一个用户账户数据库、一个商品库存数据库,现在提供一个买货的接口,当买卖成功时,扣除用户账户和商品库存,大致伪代码如下:

public void buy() {// 用户账户操作userAccount();// 商品账户操作StoreAccount();
}

在上面这个操作做,两个函数的操作必须同时成功,不然就会出现数据不一致问题,也就是需要保证事务原子性。因为设定的场景是数据在两个不同的数据库,所有没有办法利用单个数据库的事务机制,它是跨数据库的,所以需要分布式事务的机制。

class Demo {public void buy() {// try 阶段:比如去判断用户和商品的余额和存款是否充足,进行预扣款和预减库存if (!userServer.tryDeductAccount()) {// 用户预扣款失败,相关数据没有改变,返回错误即可}if (!storeService.tryDeductAccount()) {// cancel 阶段: 商品预减库存失败,因为前面进行了用户预扣款,所以需要进入cancel阶段,恢复用户账户userService.cancelDeductAccount();}// Confirm 阶段:try 成功就进行confirm阶段,这部分操作比如是将扣款成功状态和减库存状态设置为完成if (!userService.confirmDeductAccount() || !storeService.confirmDeductAccount()) {// cancel 阶段:confirm的任意阶段失败了,需要进行数据恢复(回滚)userService.cancelDeductAccount();storeService.cancelDeductAccount();}}
}

可以看出,如果后续增加更多的业务处理,就会重复添加三个阶段的业务代码,代码侵入量高。

业务状态补偿

业务状态补偿是通过给业务数据添加状态字段,在数据库中认为当前服务执行成功,标识成功,通过调用其它服务返回的业务结果,来对其本地业务进行补偿,失败则进行一些补偿操作,更新状态,从从而达到最终的数据一致性,这种方案大多在支付、银行业务场景见的比较多,很依赖业务实现方案。

MQ消息事务

消息事务的原理是 将两个事务通过消息中间件来进行异步解耦。基于可靠消息服务的方案是通过消息中间件来保证上、下游应用数据操作的一致性。假设有 订单服务、库存服务,分布可以处理 订单、库存两个任务,此时需要存在一个业务流程,将任务 订单和库存 放到同一个事物中处理,这种方式就可以借助消息中间件来实现。
在这里插入图片描述
上面大致分为两个步骤:

  1. 步骤一: 订单服务向消息中间件发布消息
    1.在订单服务开始前,首先向消息中间件发送一条信息,告诉MQ自己即将开始执行相应的业务操作
    2.消息中间件收到后将该消息持久化,但不进行投递。持久化成功后,向订单服务返回确认应答
    3.订单服务收到确认应答后,便可以开始处理对应的业务
    4.订单服务处理完后,便会向消息中间件发送 Commit 或者 Rollback 请求,该请求发送完成后,订单服务业务就算处理完成,该事务的处理过程也就结束了
    5.在消息中间件收到 Commit 后,便会向 库存服务投递消息,如果收到 Rollback 便会直接丢弃消息

如果消息中间件在最后的过程中,长时间没有收到服务A 发送的 Commit 或 Rollback 指令,这个时候就需要依靠 超时询问机制

超时询问机制:
订单服务除了实现正常的业务流程之外,还是需要提供一个可供消息中间件事务询问的接口。在消息中间件第一次收到消息后便会开始计时,如果超过规定的时间没有收到后续的指令,就会主动调用订单服务提供的事务询问接口,询问当前服务的状态,通常来说该接口会返回三种结果,中间件需要根据这三种不同的结果做出不同的处理:
提交:直接将该消息投递给服务B
回滚:直接将该消息丢弃
处理中:继续等待,重新计时

  1. 步骤二: 消息中间件向库存服务投递消息
    1.消息中间件收到订单服务的提交 Commit 指令后便会将该消息投递给库存服务,然后将自己的状态置为阻塞等待状态。库存服务收到消息中间件发送的消息后便开始处理自己的业务逻辑,处理完成后便会向消息中间件发出回应。但是在消息中间件阻塞等待的时候同样会出现问题
    正常情况:消息中间件投递完消息后,进入阻塞等待状态,在收到确认应答后便认为事务处理完成,该流程结束
    等待超时情况:在等待确认应答超时之后就会重新进行投递,直到库存服务器返回消费成功响应为止。而消息重试的次数和时间间隔都可以设置,如果最终还是不能成功进行投递,则需要人工干预。

由此可以看出来,MQ消息事务方案是实现了最终一致性,适用于高并发的场景。RocketMQ 就很好的支持了消息事务。如果只是为了实现MQ事务而引入MQ事务,势必会增加业务的复杂性,如果业务本身就因为其它需求,使用到了RocketMQ,消息事务方法不失为一种好的解决办法。

Seata (阿里开源分布式解决方案)

Seata是一个分布式事务解决方案,致力于解决分布式系统中的事务一致性问题。它提供了高效、易用的分布式事务管理功能,包括事务发起、全局事务管理、分支事务管理等。Seata具有原子性、一致性、隔离性和持久性(ACID)的特性

Seata 的设计目标是对业务无侵入,因此它是从业务无侵入的两阶段提交(全局事务)着手,在传统的两阶段上进行改进,他把一个分布式事务理解成一个包含了若干分支事务的全局事务。而全局事务的职责是协调它管理的分支事务达成一致性,要么一起成功提交,要么一起失败回滚。

Seata 架构设计

  1. TC(Transaction Coordinator):事务协调者。管理全局的分支事务的状态,用于全局性事务的提交和回滚。
  2. TM(Transaction Manager):事务管理者。用于开启、提交或回滚事务。
  3. RM(Resource Manager):资源管理器。用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接收 TC 的命令来提交或者回滚分支事务。
    在这里插入图片描述
    1.订单服务中的 TM 向 TC 申请开启一个全局事务,TC 就会创建一个全局事务并返回一个唯一的 XID
    2.订单服务中的 RM 向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务
    3.订单服务开始执行分支事务
    4.订单服务开始远程调用库存服务,此时 XID 会根据调用链进行传播
    5.订单服务中的 RM 也向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中
    6.订单服务开始执行分支事务
    7.全局事务调用处理结束后,TM 会根据有误异常情况,向 TC 发起全局事务的提交或回滚
    8.TC 协调其管辖之下的所有分支事务,决定是提交还是回滚
SpringBoot集成Seata

// 在引入seata之前,需要下载相关服务,在官网下载即可。

引入 seata 的 maven 依赖坐标,需要开启事务的方法上添加 @GlobalTransactional 注解,类似于我们单体事务添加的@Transactional。

@GlobalTransactional 
// 支付系统伪代码
public String demoService(UserInfoDTO dto) {// 订单服务orderService.addOrder(dto);// 库存服务// 模拟远程调用库存服务
}

Seata 作为一个强大的分布式事务解决方案,为我们提供了一种简单而灵活的工具来解决分布式事务问题。

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

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

相关文章

【Javascript编程实操06】1、反转数组和字符串 2、将二维数组转一维数组

前言 1、反转数组和字符串 代码: 实现效果: 2、将二维数组转一维数组 代码: 实现效果: 总结 前言 本次主要是针对Javascript阶段的字符串与数组的实操练习,共有2个实操,大家可以在实操的过程中更加深…

阅读 - 二维码扫码登录原理

在日常生活中,二维码出现在很多场景,比如超市支付、系统登录、应用下载等等。了解二维码的原理,可以为技术人员在技术选型时提供新的思路。对于非技术人员呢,除了解惑,还可以引导他更好地辨别生活中遇到的各种二维码&a…

2024批量导出公众号所有文章生成目录,这下方便找文章了

公众号历史文章太多,手机上翻起来太费劲,怎么快速找到某一天的文章呢?比如深圳卫健委这个号从2014到2024发布近万篇文章。 公众号历史文章太多,手机上翻起来太费劲,怎么快速找到某一天的文章? 如果要找2020…

主干网络篇 | YOLOv8更换主干网络之ShuffleNetV2(包括完整代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。ShuffleNetV2是一种轻量级的神经网络架构,用于图像分类和目标检测任务。它是ShuffleNet的改进版本,旨在提高模型的性能和效率。ShuffleNetV2相比于之前的版本,在保持模型轻量化的同时&am…

用户视角的比特币和以太坊外围技术整理

1. 引言 要点: 比特币L2基本强调交易内容的隐蔽性,P2P交易(尤其是支付)成为主流,给用户带来一定负担(闪电网络)在以太坊 L2 中,一定程度上减少了交易的隐蔽性,主流是实…

【Linux进程状态】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、直接谈论Linux的进程状态 看看Linux内核源代码怎么说 1.1、R状态 -----> 进程运行的状态 1.2、S状态 -----> 休眠状态(进程在等待“资源”就绪) 1.3、T状…

突破编程_前端_JS编程实例(工具栏组件)

1 开发目标 工具栏组件旨在模拟常见的桌面软件工具栏,所以比较适用于 electron 的开发,该组件包含工具栏按钮、工具栏分割条和工具栏容器三个主要角色,并提供一系列接口和功能,以满足用户在不同场景下的需求: 点击工具…

Vue+SpringBoot打造音乐平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首音乐4.2 新增音乐4.3 新增音乐订单4.4 查询音乐订单4.5 新增音乐收藏 五、免责说明 一、摘要 1.1 项目介绍 基于微信小程序JAVAVueSpringBootMySQL的音乐平台,包含了音乐…

tigramite教程(五)使用TIGRAMITE 进行自助聚合和链接置信度量化

使用TIGRAMITE 进行自助聚合和链接置信度量化 自助聚合(Bagging)和置信度估计例子数据生成模型基本的PCMCIBagged-PCMCI使用优化后的pc_alpha进行自举聚合使用优化的pc_alpha进行CMIknn的自举聚合 TIGRAMITE是一个用于时间序列分析的Python模块。它基于P…

如何批量获取公众号所有文章的阅读数点赞数和留言数导出excel?

如何批量获取公众号所有文章的阅读数点赞数和留言数导出excel?我写了个脚本批量抓取,导出的excel文章数据包含文章日期,文章标题,文章链接,文章简介,文章作者,文章封面图,是否原创&a…

《圣斗士星矢:纵横宇宙》(上)AI制作真人版大电影

《圣斗士星矢:纵横宇宙》(上)AI制作真人版大电影 平行宇宙,黑暗来袭,十二件黄金圣衣合体成为究极秘密武器! 《圣斗士星矢:纵横宇宙》(上)电影开场,星矢等一众…

网络通信与网络协议

网络编程是指利用计算机网络实现程序之间通信的一种编程方式。在网络编程中,程序需要通过网络协议(如 TCP/IP)来进行通信,以实现不同计算机之间的数据传输和共享。在网络编程中,通常有三个基本要素 IP 地址:定位网络中某台计算机端口号port:定…

Nginx高级技术: 代理缓存配置

一、缓存说明 Nginx缓存,Nginx 提供了一个强大的反向代理和 HTTP 服务器功能,同时也是一个高效的缓存服务器。一般情况下系统用到的缓存有以下三种: 1、服务端缓存:缓存存在后端服务器,如 redis。 2、代理缓存&#…

6.【Linux】进程间通信(管道命名管道||简易进程池||简易客户端服务端通信)

介绍 进程间通信的方式 1.Linux原生支持的管道----匿名和命名管道 2.System V-----共享内存、消息队列、信号量 3.Posix------多线程、网路通信 进程间通信目的 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。…

UE4_调试工具_绘制调试球体

学习笔记,仅供参考! 效果: 步骤: 睁开眼睛就是该变量在此蓝图的实例上可公开编辑。 勾选效果:

VUE3生命周期钩子

概念 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在…

文件包含例子

一、常见的文件包含函数 php中常见的文件包含函数有以下四种: include() require() include_once() require()_once() include与require基本是相同的,除了错误处理方面: include(),只生成警告(E_WARNING)&#x…

基于java+springboot+vue实现的自习室管理和预约系统(文末源码+Lw)23-177

摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装自习室管理和预约系统软件来发挥其高效地信息处理的作用&a…

Python爬取淘宝商品评价信息实战

文章目录 一、分析需要爬取的页面二、实现爬取商品评价信息的代码1、通过解析显示评价信息的元素获取商品评价信息2、通过mitmproxy代理进行流量抓包获取商品评价信息 三、附-完整代码 前期出了一个《爬取京东商品评价信息实战》的教程,最近又有网友提到要出一个爬淘…

案例分析篇13:系统分析与设计考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…