11.Rocket解决分布式事务

news2024/12/29 10:13:18

highlight: arduino-light

两阶段提交协议

原文链接:https://blog.csdn.net/fenglibing/article/details/92417739

两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。一般分为协调器TC和若干事务执行者两种角色,这里的事务执行者就是具体的数据库,协调器可以和事务执行器在一台机器上。

image.png

我们根据上面的图来看看主要流程:

1) 我们的应用程序(client)发起一个开始请求到TC(transaction);

2) TC先将prepare消息写到本地日志,之后向所有的业务系统发起prepare消息。以支付宝转账到余额宝为例,TC给A的prepare消息是通知支付宝数据库相应账目扣款1万,TC给B的prepare消息是通知余额宝数据库相应账目增加1w。为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证的效果,如果没有本地日志(凭证),出问题容易死无对证;

3) 业务系统收到prepare消息后,执行具体本机事务,但不会进行commit,如果成功返回yes,不成功返回no。同理,返回前都应把要返回的消息写到日志里,当作凭证。

4) TC收集所有执行器返回的消息,如果所有执行器都返回yes,那么给所有执行器发生送commit消息,执行器收到commit后执行本地事务的commit操作;如果有任一个执行器返回no,那么给所有执行器发送abort消息,执行器收到abort消息后执行事务abort操作。

注:TC或Si把发送或接收到的消息先写到日志里,主要是为了故障后恢复用。如某一Si从故障中恢复后,先检查本机的日志,如果已收到commit,则提交,如果abort则回滚。如果是yes,则再向TC询问一下,确定下一步。如果什么都没有,则很可能在prepare阶段Si就崩溃了,因此需要回滚。

现如今实现基于两阶段提交的分布式事务也没那么困难了,如果使用java,那么可以使用开源软件atomikos(http://www.atomikos.com/),来快速实现。)

分布式事务选型

https://blog.csdn.net/qq_42556214/article/details/105796048

分布式事务性能问题

不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差,根本不适合高并发的系统。为什么?

1)两阶段提交涉及多次节点间的网络通信,通信时间太长!
2)事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多!

正是由于分布式事务存在很严重的性能问题,大部分高并发服务都在避免使用,往往通过其他途径来解决数据一致性问题。

使用消息队列来避免分布式事务

如果仔细观察生活的话,生活的很多场景已经给了我们提示。

比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,而是给你一张小票,然后让你拿着小票到出货区排队去取。

为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强(并发量更高)。

还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当支付宝账户扣除1万后,我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性。

那么我们如何可靠保存凭证(消息)有两种方法:

1)业务数据与消息耦合的方式:数据库

支付宝在完成扣款的同时,同时在数据库记录消息数据,消息数据与业务数据保存在同一数据库实例里(消息记录表表名为message)。

Begin transaction 
       update A set amount=amount-10000 where userId=1; 
       insert into message(userId, amount,status) values(1, 10000, 1); 
End transaction 
commit;

上述事务能保证只要用户支付宝账户里被扣了钱,消息一定能保存下来。

1.支付宝减少余额,并插入Message1表

2.定时器扫描message表,请求余额宝,余额宝处理成功则将数据置为已处理

3.余额宝增加余额

假如在第一步处理失败 - 业务数据回滚 没影响

假如在第二步处理失败 - 余额宝处理成功,没有置为已处理,那么定时器仍然会去扫描这条消息

假如在第三步处理失败 - 不会返回成功响应,也不会将数据置为已处理那么定时器仍然会去扫描这条消息

2)业务与消息解耦方式:消息队列

上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。

a)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;

b)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;

c)当支付宝扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;

d)对于那些未确认的消息或者取消的消息,需要有一个定时任务去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。

优点:消息数据独立存储,降低业务系统与消息系统间的耦合;

缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。

RocketMq解决分布式事务方案

比如一个下订单扣库存的动作,这两个服务分别操作订单库和库存库,属于分布式事务范畴了,如果mq不支持事务,那么可能做法是:

```md //step1:开启本地事务

//step2:订单库新增一条记录

//step3:向mq发送订单消息,用于扣库存

//step4:提交事务/回滚事务 ```

该方案在正常情况下没有问题,但是一些异常情况下就有了问题:

1.如果step3执行后,在step4执行前jvm进程or服务器宕机,事务没有成功提交,订单数据库回滚没变化和但是库存数据库减少,导致两个库数据不一致。

2.由于消息是在事务提交之前提交,发送的消息内容是订单实体的内容,会造成在消费端进行消费时如果需要去验证订单是否存在时可能出现订单不存在,该问题也会存在,因为消费端速度很快的话。

对于生成订单(DB操作)和发送消息是一个事务内的动作,因此要保证要么全部成功,要么回滚,因此可以采用rocketmq的事务消息来解决。

rocketmq事务消息解决分布式事务,实现最终数据一致性,思想就是xa协议2pc。 ```md //step1:向mq发送事务订单消息 用于扣库存。此时事务消息不可见

//step2:开启本地事务,订单库新增一条记录

//step3:触发本地逻辑:判断本地事务是否执行成功。

本地事务执行成功提交事务并提交事务消息即可。此时事务消息可见

本地事务执行失败提交事务并回滚事务消息即可。此时事务消息不可见

假设出现异常,本地事务或者事务消息是提交还是回滚未知,此时可以通过事务回查机制判断本地事务是否执行成功。

本地事务执行成功提交事务并提交事务消息即可。此时事务消息可见

本地事务执行失败提交事务并回滚事务消息即可。此时事务消息不可见

```

事务回查相关参数:

transactionTimeout==60s即事务回查超时时间。

transactionCheckMax==15即最大回查次数。

listener是DefaultTransactionalMessageCheckListener。

功能:读取当前half的half queueoffset,然后从op half拉取32条消息保存到removeMap,如果half queueoffset处的消息在removeMap中, 则说明该prepare消息被处理过了,然后读取下一条prepare消息,如果prepare不在removeMap中,说明是需要回查的,此时broker作为client端,向服务端producer发送回查命令,最后由producer返回回查结果更新原prepare消息。 ```md 考虑几个事务消息的异常状态: ​ 1.preprare消息发送成功,本地事务执行成功,但是producer宕机 ​ 该情况broker会进行回查事务状态,从而提交事务,发送消息给下游系统。 ​ 2.preprare消息发送成功,本地事务执行过程中producer宕机了 ​ 事务执行过程宕机了,那么数据库自动会回滚事务,事务就是没执行成功,因此broker回查从而删除preprae消息。 ​ 3.preprare消息发送成功,本地事务执行成功,但是因为broker宕机发送commit消息给broker失败(发送给prepare消息接收的那台broker), ​ 启动该broker,broker回查到事务执行成功,从而提交消息,发送消息给下游系统进行消费,该情况会导致下游有长时间延迟才收到消息消费。 ​ 4.客户端消费消息失败了,怎么办? ​ rocketmq给出的方案是人工解决,这样的情况不能多,如果多了,需要优化业务和代码,实际用rocketmq的事务消息,客户端消费失败情况是少的,比如扣库存动作,基本都是成功的。客户端消费失败的情况通常是通过对账根据业务情况解决。

使用rocketmq来保证分布式事务属于消息一致性方案,通过消息中间件保证上、下游应用数据操作的一致性。基本思路是将本地操作和发送消息放在一个事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作。 ``` 消息方案从本质上讲是将分布式事务转换为两个本地事务,然后依靠下游业务的重试机制达到最终一致性。基于消息的最终一致性方案对应用侵入性也很高,应用需要进行大量业务改造,成本较高。

RocketMQ 中的分布式事务实现

在 RocketMQ 中的事务实现中,增加了事务反查的机制来解决事务消息提交失败的问题。

如果 Producer 也就是订单系统,在提交或者回滚事务消息时发生网络异常,RocketMQ的 Broker 没有收到提交或者回滚的请求,Broker 会定期去 Producer 上反查这个事务对应的本地事务的状态,然后根据反查结果决定提交或者回滚这个事务。为了支撑这个事务反查机制,我们的业务代码需要实现一个反查本地事务状态的接口,告知RocketMQ 本地事务是成功还是失败。

在我们这个例子中,反查本地事务的逻辑也很简单,我们只要根据消息中的订单 ID,在订单库中查询这个订单是否存在即可,如果订单存在则返回成功,否则返回失败。

RocketMQ 会自动根据事务反查的结果提交或者回滚事务消息。

这个反查本地事务的实现,并不依赖消息的发送方,也就是订单服务的某个实例节点上的任何数据。

这种情况下,即使是发送事务消息的那个订单服务节点宕机了,RocketMQ 依然可以通过其他订单服务的节点来执行反查,确保事务的完整性。

综合上面讲的通用事务消息的实现和 RocketMQ 的事务反查机制,使用 RocketMQ 事务消息功能实现分布式事务的流程如下图

image.png

分布式事务性能

为了方便大家理解,我们再来举一个银行转账的示例(和上一个例子差不多):

比如,Bob向Smith转账100块。

在单机环境下,执行事务的情况,大概是下面这个样子:

image.png

当用户增长到一定程度,Bob和Smith的账户及余额信息已经不在同一台服务器上了,那么上面的流程就变成了这样:

image.png

这时候你会发现,同样是一个转账的业务,在集群环境下,耗时居然成倍的增长,这显然是不能够接受的。那如何来规避这个问题?

大事务 = 小事务 + 异步

将大事务拆分成多个小事务异步执行。这样基本上能够将跨机事务的执行效率优化到与单机一致。转账的事务就可以分解成如下两个小事务:

image.png

图中执行本地事务(Bob账户扣款)和发送异步消息应该保证同时成功或者同时失败,也就是扣款成功了,发送消息一定要成功,如果扣款失败了,就不能再发送消息。那问题是:我们是先扣款还是先发送消息呢?

首先看下先发送消息的情况,大致的示意图如下:

image.png

存在的问题是:如果消息发送成功,但是扣款失败,消费端就会消费此消息,进而向Smith账户加钱。

先发消息不行,那就先扣款吧,大致的示意图如下:

image.png

存在的问题跟上面类似:如果扣款成功,发送消息失败,就会出现Bob扣钱了,但是Smith账户未加钱。

可能大家会有很多的方法来解决这个问题,比如:直接将发消息放到Bob扣款的事务中去,如果发送失败,抛出异常,事务回滚。这样的处理方式也符合“恰好”不需要解决的原则。

RocketMQ支持事务消息,下面来看看RocketMQ是怎样来实现的?

image.png

RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址,第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改消息的状态。

细心的你可能又发现问题了,如果确认消息发送失败了怎么办?RocketMQ会定期扫描消息集群中的事物消息,如果发现了Prepared消息,它会向消息发送端(生产者)确认,Bob的钱到底是减了还是没减呢?如果减了是回滚还是继续发送确认消息呢?

RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

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

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

相关文章

【Vue3】学习笔记-ref函数、reactive函数

ref函数、reactive函数 Ref函数reactive函数 Ref函数 作用:定义一个响应式的数据语法:const xxxref(initValue) 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。JS中操作数据:xxx.value模板中读取数据:不需要.va…

地理数据处理-地理空间表的导入与查询

前言 现在大部分数据库都支持地理空间的数据存储,可以方便的与GIS平台调用,比如Geoserver、ArcGis, 但是如果使用不规范的空间表去发布图层预览可能会出现各种问题,比如坐标系错误,无法聚焦。 本文将举例最常见的SQLServer和Post…

Godot引擎 4.0 文档 - 手册 - 2D

本文为Google Translate英译中结果,DrGraph在此基础上加了一些校正。英文原版页面: 2D — Godot Engine (stable) documentation in English 画布层 视口和画布项目 CanvasItem是所有 2D 节点的基础,无论是常规的 2D 节点,例如…

深入理解微分、积分电路!搞懂PID控制原理就这么简单!

很多朋友觉得PID是遥不可及,很神秘,很高大上的一种控制,对其控制原理也很模糊,只知晓概念性的层面,知其然不知其所以然,那么本期从另类视角来探究微分、积分电路的本质,意在帮助理解PID的控制原…

智慧排水监测系统有什么作用?

随着城市化进程的加速,城市排水系统的压力不断增加。然而,当前城市排水系统面临着管理效率低下、水资源浪费和洪涝灾害等问题。为了解决这些问题,智慧排水监测系统逐渐成为了新的解决方案。本文将为大家详细介绍智慧排水监测系统的作用以及智…

微软公布量子超级计算机路线图

光子盒研究院 6月22日,微软公布了三个重要的量子计算公告。首先,公司宣布它已经实现了通往量子超级计算机的六步路线图的第一个里程碑,并发表了一篇经同行评议的研究论文来证明这一成就。 这家科技巨头的第二项公告是将其人工智能&#xff08…

堆——“数据结构与算法”

各位CSDN的uu们你们好呀,今天小雅兰的内容仍旧是二叉树,此刻分享的内容是一种特殊的二叉树,也就是堆了。下面,让我们进入堆的世界吧!!! typedef int HeapDataType; typedef struct Heap {HeapDa…

MES系统是什么?它如何帮助企业提高生产效率?

随着制造业的发展,越来越多的企业开始使用全面的制造执行系统(MES)来管理其生产过程。那么,MES系统到底是什么呢?它又是如何帮助企业提高生产效率的呢?本文将为大家详细介绍。 一、MES系统的概念 MES系统是…

Leetcode 剑指 Offer II 032. 有效的变位词

题目难度: 简单 原题链接 今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定两个字符串 s 和 t ,编写一个函数来判断它们是不…

基于物联网技术的智能配电室综合监控系统设计与产品选型

摘要:配电室是电力系统的重要基础设施,可以保障供配电系统的安全稳定运行。但是,配电室数量多、部署分散、管理复杂,人工现场巡检管理方式费时费工、错误率高,如果发生故障隐患,往往无法及时发现。针对配电…

Sparse Fuse Dense: 向高质量的深度补全3D检测迈进

点云的稀疏性:在远距离和遮挡区域提供的信息较差,导致难以生成精确的3D边界框。 出现了多模态融合。 图像和点云的不同表示方式使得它们难以融合,导致性能不佳。 论文提出了一种新颖的多模态框架SFD(Sparse Fuse Dense&#xf…

二层交换机工作原理与MAC地址介绍

目录 MAC地址介绍 MAC地址的组成 MAC地址分类 MAC地址的作用 二层交换机介绍 MAC地址表的定义 MAC地址表项类型 二层交换机对数据帧的处理动作 MAC地址介绍 MAC地址(Media Access Control Address),直译为媒体存取控制位地址 MAC地址的组成 MA…

华为OD机试真题(Java),图片整理(100%通过+复盘思路)

一、题目描述 Lily上课时使用字母数字图片教小朋友们学习英语单词,每次都需要把这些图片按照大小(ASCII码值从小到大)排列收好。请大家给Lily帮忙,通过代码解决。 Lily使用的图片使用字符"A"到"Z"、“a"…

JMeter 环境配置

目录 前言: 一、 JDK安装 二、 安装JMeter 三、 安装插件 前言: JMeter是一款功能强大的性能测试工具,用于模拟多种负载条件下的应用程序行为,环境配置是jmeter学习的第一步,每次换电脑就得重新配置,为…

Spark RDD dataframe嘿嘿

RDD(Resilient Distributed Datasets)可扩展的弹性分布式数据集,RDD是spark最基本的数据抽象,RDD表示一个只读、分区且不变的数据集合,是一种分布式的内存抽象,与分布式共享内存(Distributed Sh…

面试题:希尔排序是一种稳定排序吗?

面试题:希尔排序是一种稳定排序吗? 对于算法的稳定性,有这样一个记忆技巧,不稳定排序是"快些选队",对应于快速排序/希尔排序/选择排序/堆排序。希尔排序也名列其中,因此也是一种不稳定排序&…

CODESYS 数组类型变量(ARRAY)使用介绍

博途PLC数组类型变量使用介绍请参看下面文章博客: 博途1200/1500PLC上升沿下降沿指令编程应用技巧(bool数组)_博途上升沿指令_RXXW_Dor的博客-CSDN博客博途PLC的下降沿和上升沿指令,在控制系统编程时经常会使用。和SMARTS7-200有所不同,遵循IEC-6113标准提供的上升沿下降沿…

【初识C语言(6)】指针+结构体

文章目录 1. 指针1.1 内存1.2 指针变量的大小 2. 结构体 1. 指针 想要学好指针,首先必须要先了解内存。 1.1 内存 内存介绍 内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。 所以为了有效的使用内存,就把内存划分…

​LeetCode解法汇总1401. 圆和矩形是否有重叠

目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣 描述: 给你一个以 (radius, xCenter, yCenter) 表示的圆和一个与坐标轴平行的矩形 (x1…

第三方医药数据供应商有哪些?--数据业务介绍

第三方医药数据供应商主要是为医药企业、健康机构、学术研究、药物研发等提供医药相关数据的收集、整理、分析和应用服务。随着医药市场的需求衍生了许多各高垂直领域的医药数据供应商,这也导致了大家对医药数据供应商涉及领域认识的片面性。 故本文重点介绍各医药…