RabbitMQ 存储机制

一、消息存储机制

不管是持久化的消息还是非持久化的消息都可以被写入到磁盘。持久化的消息在到达队列时就被写入到磁盘,非持久化的消息一般只保存在内存中,在内存吃紧的时候会被换入到磁盘中,以节省内存空间。这两种类型的消息的落盘处理都在 RabbitMQ 的“持久层”中完成。

持久层是一个逻辑上的概念,实际包含两个部分:

(1)队列索引(rabbit_queue_index

rabbit_queue_index 负责维护队列中落盘消息的信息,包括消息的存储地点、是否已被交付给消费者、是否已被消费者 ack 等。每个队列都有与之对应的一个 rabbit_queue_index

(2)消息存储(rabbit_msg_store

rabbit_msg_store 以键值对的形式存储消息,它被所有队列共享,在每个节点中有且只有一个。
从技术层面上来说,rabbit_msg_store 具体还可以分为:

  • <1> msg_store_persistent:负责持久化消息的持久化,重启后消息不会丢失
  • <2> msg_store_transient:负责非持久化消息的持久化,重启后消息会丢失。
1. 消息存储

消息(包括消息体、属性和 headers)可以直接存储在 rabbit_queue_index 中,也可以被保存在 rabbit_msg_store 中。默认在 $RABBITMQ_HOME/var/lib/mnesia/rabbit@$HOSTNAME/ 路径下包含 queuesmsg_store_persistentmsg_store_transient 这 3 个文件夹,其分别存储对应的信息(不同版本目录位置有所不同):

 

最佳的配备是较小的消息存储在 rabbit_queue_index 中而较大的消息存储在 rabbit_msg_store 中。这个消息大小的界定可以通过 queue_index_embed_msgs_below 来配置,默认大小为 4096,单位为 B。注意这里的消息大小是指消息体、属性及 headers 整体的大小。当一个消息小于设定的大小阈值时就可以存储在 rabbit_queue_index 中,这样可以得到性能上的优化。

rabbit_queue_index 中以顺序(文件名从 0 开始累加)的段文件来进行存储,后缀为“.idx”,每个段文件中包含固定的 SEGMENT_ENTRY_COUNT 条记录,SEGMENT_ENTRY_COUNT 默认值为16384每个 rabbit_queue_index 从磁盘中读取消息的时候至少要在内存中维护一个段文件,所以设置 queue_index_embed_msgs_below 值的时候要格外谨慎,一点点增大也可能会引起内存爆炸式的增长

经过 rabbit_msg_store 处理的所有消息都会以追加的方式写入到文件中,当一个文件的大小超过指定的限制(file_size_limit)后,关闭这个文件再创建一个新的文件以供新的消息写入。文件名(文件后缀是“.rdq”)从 0 开始进行累加,因此文件名最小的文件也是最老的文件。在进行消息的存储时,RabbitMQ 会在 ETS(Erlang Term Storage)表中记录消息在文件中的位置映射(Index)和文件的相关信息(FileSummary

2. 消息读取

在读取消息的时候,先根据消息的ID(msg_id)找到对应存储的文件,如果文件存在并且未被锁住,则直接打开文件,从指定位置读取消息的内容。如果文件不存在或者被锁住了,则发送请求由 rabbit_msg_store 进行处理。

3. 消息删除

消息的删除只是从 ETS 表删除指定消息的相关信息,同时更新消息对应的存储文件的相关信息。执行消息删除操作时,并不立即对在文件中的消息进行删除,也就是说消息依然在文件中,仅仅是标记为垃圾数据而已。当一个文件中都是垃圾数据时可以将这个文件删除。当检测到前后两个文件中的有效数据可以合并在一个文件中,并且所有的垃圾数据的大小和所有文件(至少有 3 个文件存在的情况下)的数据大小的比值超过设置的阈值 GARBAGE_FRACTION(默认值为 0.5)时才会触发垃圾回收将两个文件合并。

执行合并的两个文件一定是逻辑上相邻的两个文件。执行合并时首先锁定这两个文件,并先对前面文件中的有效数据进行整理,再将后面文件的有效数据写入到前面的文件,同时更新消息在 ETS 表中的记录,最后删除后面的文件。

二、队列结构实现

1. 队列结构

通常队列由两部分组成:
(1)rabbit_amqqueue_process
rabbit_amqqueue_process 负责协议相关的消息处理,即接收生产者发布的消息、向消费者交付消息、处理消息的确认(包括生产端的 confirm 和消费端的 ack)等。
(2)backing_queue

backing_queue 是消息存储的具体形式和引擎,并向 rabbit_amqqueue_process 提供相关的接口以供调用。

如果消息投递的目的队列是空的,并且有消费者订阅了这个队列,那么该消息会直接发送给消费者,不会经过队列这一步。而当消息无法直接投递给消费者时,需要暂时将消息存入队列,以便重新投递。

消息存入队列后,不是固定不变的,它会随着系统的负载在队列中不断地流动,消息的状态会不断发生变化。RabbitMQ 中的队列消息可能会处于以下4种状态:
(1)alpha:消息内容(包括消息体、属性和 headers)和消息索引都存储在内存中。
(2)beta:消息内容保存在磁盘中,消息索引保存在内存中。
(3)gamma:消息内容保存在磁盘中,消息索引在磁盘和内存中都有。
(4)delta:消息内容和索引都在磁盘中。

对于持久化的消息,消息内容和消息索引都必须先保存在磁盘上,才会处于上述状态中的一种。而 gamma 状态的消息是只有持久化的消息才会有的状态。

RabbitMQ 在运行时会根据统计的消息传送速度定期计算一个当前内存中能够保存的最大消息数量(target_ram_count),如果 alpha 状态的消息数量大于此值时,就会引起消息的状态转换,多余的消息可能会转换到 beta 状态、gamma 状态或者 delta 状态。区分这 4 种状态的主要作用是满足不同的内存和 CPU 需求。alpha 状态最耗内存,但很少消耗 CPU。delta 状态基本不消耗内存,但是需要消耗更多的 CPU 和磁盘 I/O 操作。delta 状态需要执行两次 I/O 操作才能读取到消息,一次是读消息索引(从 rabbit_queue_index 中),一次是读消息内容(从 rabbit_msg_store 中);beta和 gamma 状态都只需要一次 I/O 操作就可以读取到消息(从 rabbit_msg_store 中)。

对于普通的没有设置优先级和镜像的队列来说,backing_queue 的默认实现是 rabbit_variable_queue,其内部通过 5 个子队列 Q1、Q2、Delta、Q3 和 Q4 来体现消息的各个状态。整个队列包括 rabbit_amqqueue_process 和 backing_queue 的各个子队列:

其中 Q1、Q4 只包含 alpha 状态的消息,Q2 和 Q3 包含 beta 和 gamma 状态的消息,Delta 只包含 delta 状态的消息。一般情况下,消息按照 Q1→Q2→Delta→Q3→Q4 这样的顺序步骤进行流动,但并不是每一条消息都一定会经历所有的状态,这取决于当前系统的负载状况。

2. 消息状态流转

从 Q1 至 Q4 基本经历内存到磁盘,再由磁盘到内存这样的一个过程,如此可以在队列负载很高的情况下,能够通过将一部分消息由磁盘保存来节省内存空间,而在负载降低的时候,这部分消息又渐渐回到内存被消费者获取,使得整个队列具有很好的弹性。消费者获取消息也会引起消息的状态转换。当消费者获取消息时,首先会从 Q4 中获取消息,如果获取成功则返回。如果 Q4 为空,则尝试从 Q3 中获取消息,系统首先会判断 Q3 是否为空,如果为空则返回队列为空,即此时队列中无消息。如果 Q3 不为空,则取出 Q3 中的消息,进而再判断此时 Q3 和 Delta 中的长度,如果都为空,则可以认为 Q2、Delta、Q3、Q4 全部为空,此时将 Q1 中的消息直接转移至 Q4,下次直接从 Q4 中获取消息。如果 Q3 为空,Delta 不为空,则将 Delta 的消息转移至 Q3 中,下次可以直接从 Q3 中获取消息。在将消息从 Delta 转移到 Q3 的过程中,是按照索引分段读取的,首先读取某一段,然后判断读取的消息的个数与 Delta 中消息的个数是否相等,如果相等,则可以判定此时 Delta 中已无消息,则直接将 Q2 和刚读取到的消息一并放入到 Q3 中;如果不相等,仅将此次读取到的消息转移到 Q3。消息数据大致流向如下:

 

即: Q1 消息最终会流向 Q4,Q2、Delta 消息最终会流向 Q3,消费者优先从 Q4 中读取消息,若 Q4 为空再从 Q3 中读取,若 Q3、Q4 都为空,则可认为当前消息队列为空。

通常在负载正常时,如果消息被消费的速度不小于接收新消息的速度,对于不需要保证可靠不丢失的消息来说,极有可能只会处于 alpha 状态。对于 durable 属性设置为 true 的消息,它一定会进入 gamma 状态,并且在开启 publisher confirm 机制时,只有到了 gamma 状态时才会确认该消息已被接收,若消息消费速度足够快、内存也充足,这些消息也不会继续走到下一个状态。

当系统负载较高,消息堆积较多,处理每个消息的平均开销增大时,可以有以下 3 种措施进行应对:
(1)增加 prefetch_count 的值,即一次发送多条消息给消费者,加快消息被消费的速度;
(2)采用 multiple ack,降低处理 ack 带来的开销;
(3)流量控制。

三、惰性队列

RabbitMQ 从 3.6.0 版本开始引入了惰性队列(Lazy Queue)的概念。惰性队列会尽可能地将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机,或者由于维护而关闭等)致使长时间内不能消费消息而造成堆积时,惰性队列就很有必要了。

默认情况下,当生产者将消息发送到 RabbitMQ 的时候,队列中的消息会尽可能地存储在内存之中,这样可以更加快速地将消息发送给消费者。即使是持久化的消息,在被写入磁盘的同时也会在内存中驻留一份备份。当 RabbitMQ 需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的时间,也会阻塞队列的操作,进而无法接收新的消息

惰性队列会将接收到的消息直接存入文件系统中,而不管是持久化的或者是非持久化的,这样可以减少了内存的消耗,但是会增加 I/O 的使用,如果消息是持久化的,那么这样的 I/O 操作不可避免,惰性队列和持久化的消息可谓是“最佳拍档”。注意如果惰性队列中存储的是非持久化的消息,内存的使用率会一直很稳定,但是重启之后消息一样会丢失。惰性队列和普通队列相比,只有很小的内存开销。

队列具备两种模式:default 和 lazylazy 模式即为惰性队列的模式,可以通过调用 channel.queueDeclare 方法的时候在参数 x-queue-mode 中设置,也可以通过 Policy 的方式设置,如果一个队列同时使用这两种方式设置,那么 Policy 的方式具备更高的优先级。
发送消息时,惰性队列性能比普通队列好,出现性能偏差的原因是普通队列会由于内存不足而不得不将消息换页至磁盘。如果有消费者消费时,惰性队列会耗费将近 40MB 的空间来发送消息。

如果要将普通队列转变为惰性队列,那么我们需要忍受同样的性能损耗,首先需要将缓存中的消息换页至磁盘中,然后才能接收新的消息。反之,当将一个惰性队列转变为普通队列的时候,和恢复一个队列执行同样的操作,会将磁盘中的消息批量地导入到内存中。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

随机性、熵与随机数生成器:解析伪随机数生成器(PRNG)和真随机数生成器(TRNG)

随机性在诸多领域中扮演着至关重要的角色,涵盖密码学、仿真和机器学习等方面。因为随机性为无偏决策、不可预测序列和安全加密提供了基础。然而生成随机数是一项复杂的任务,理解伪随机数生成(pseudo-random number generation, PRNG)与真随机数生成(true random number generat…

从零开始点亮一个LED灯 —— keil下载、新建工程、版本烧录、面包板使用、实例代码

一、keil下载 参考视频&#xff1a;Keil5安装教程视频 (全套资料51和32皆可用Keil5编译设置)_哔哩哔哩_bilibili 视频内容包括下载链接、安装教程、库导入&#xff0c;非常详细&#xff01; 二、新建工程 2.1.使用stm32CubeMX新建工程 10. 使用STM32CubeMX新建工程 — [野…

嵌入式硬件电子电路设计(三)电源电路之负电源

引言&#xff1a;在对信号线性度放大要求非常高的应用需要使用双电源运放&#xff0c;比如高精度测量仪器、仪表等;那么就需要给双电源运放提供正负电源。 目录 负电源电路原理 负电源的作用 如何产生负电源 负电源能作功吗&#xff1f; 地的理解 负电压产生电路 BUCK电…

互斥量的使用

官方的描述 互斥量主要是对于共享资源的保护 其中参数要注意 osMutexRecursive&#xff1a;//递归互斥量 互斥锁嵌套属性&#xff0c;同一个线程可以在不锁定自身的情况下多次使用互斥锁。每当拥有互斥锁的线程获得互斥锁时&#xff0c;锁计数就会增加。互斥锁也必须被释放多次…

商务英语学习柯桥学外语到泓畅-老外说“go easy on me”是什么意思?

在口语中“go easy on sb ”这个短语是很常见的 01 go easy on me 怎么理解&#xff1f; 在口语中&#xff0c;“go easy on me”是一个非常常见的表达&#xff0c;通常表示请求对方在某方面对自己宽容一些&#xff0c;不要对自己太过苛刻或严厉。 短语&#xff08;go&#xff…

vscode在cmake config中不知道怎么选一个工具包?select a kit

vscode在cmake config中不知道怎么选一个工具包&#xff0c;或者发现一直在用VS的工具包想换成自己的工具包。select a kit vscode在cmake config中不知道怎么选一个工具包&#xff0c;或者发现一直在用VS的工具包想换成自己的工具包。select a kit 1.在VSCode中 按ctrlshift…

SpringBoot【实用篇】- 热部署

文章目录 目标:1.手动启动热部署2.自动启动热部署4.禁用热部署 目标: 手动启动热部署自动启动热部署热部署范围配置关闭热部署 1.手动启动热部署 当我们没有热部署的时候&#xff0c;我们必须在代码修改完后再重启程序&#xff0c;程序才会同步你修改的信息。如果我们想快速查…

AI 原生时代,更要上云:百度智能云云原生创新实践

本文整理自百度云智峰会 2024 —— 云原生论坛的同名演讲。 我今天分享的主题&#xff0c;是谈谈在云计算和 AI 技术快速发展和深入落地的背景下&#xff0c;百度智能云在云原生的基础设施产品和技术层面做的一些创新实践。 毋庸置疑&#xff0c;过去十几年云计算和 AI 技术是…

Java项目实战II基于Java+Spring Boot+MySQL的植物健康系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 基于Java、…

BGP路径属性与路由反射器

前言 IBGP水平分割规则用于防止AS内部产生环路&#xff0c;在很大程度上杜绝了IBGP路由产生环路的可能性&#xff0c;但是同时也带来了新的问题&#xff1a;BGP路由在AS内部只能传递一跳&#xff0c;如果建立IBGP对等体全互联模型又会加重设备的负担。 BGP 路径属性 AS_Path …

uniapp学习(010-2 实现抖音小程序上线)

零基础入门uniapp Vue3组合式API版本到咸虾米壁纸项目实战&#xff0c;开发打包微信小程序、抖音小程序、H5、安卓APP客户端等 总时长 23:40:00 共116P 此文章包含第113p的内容 文章目录 抖音小程序下载抖音开发者工具先去开发者工具里进行测试 抖音开放平台配置开始打包上传…

无线基础配置

配置图 各部分配置 AC1 vlan b [AC6605]vlan batch 10 20 100 Info: This operation may take a few seconds. Please wait for a moment...done. [AC6605]int [AC6605]interface g [AC6605]interface GigabitEthernet 0/0/2 [AC6605-GigabitEthernet0/0/2]port …

影刀RPA实战:识别简单计算验证码

1.官方计算验证码 基于影刀AI引擎的验证码识别指令&#xff0c;该指令不是长期免费&#xff0c;有一定的免费额度&#xff0c;用完之后需要我们到影刀官方充值。 上图使我们要识别的计算验证码 影刀指令代码&#xff1a; 配置我们选择计算题&#xff0c;文件路径本次指定本地…

HarmonyOS:UIAbility组件概述

一、概述 UIAbility组件是一种包含UI的应用组件&#xff0c;主要用于和用户交互。 UIAbility的设计理念&#xff1a; 原生支持应用组件级的跨端迁移和多端协同。支持多设备和多窗口形态。 UIAbility划分原则与建议&#xff1a; UIAbility组件是系统调度的基本单元&#xff0c…

单链表的基本操作实现

定义 链表节点长这个样子&#xff0c;数据域data指针域next指向下一个结点 typedef struct lnode {int data;struct lnode *next; }lnode ,*linklist; 初始化 /*初始化*/ linklist f1(){linklist l(linklist)malloc(sizeof(lnode));l->nextNULL;return l; }int main(){l…

C++ 优先算法——复写零(双指针)

目录 题目&#xff1a;复写零 1. 题目解析 2. 算法原理 一. 先找到最后一个“复写”数 处理边界情况 二. 复写操作 3. 代码实现 题目&#xff1a;复写零 1. 题目解析 题目截图&#xff1a; 该题目要求的与移动零相似&#xff0c;都要在一个数组上进行操作&#xff0c;…

使用linuxdeployqt打包Qt程序问题及解决方法

dpkg: 处理归档 libmysqlclient18_5.6.25-0ubuntu1_amd64.deb (--install)时出错&#xff1a; 预依赖问题 - 将不安装libmysqlclient18:amd64 在处理时有错误发生&#xff1a; libmysqlclient18_5.6.25-0ubuntu1_amd64.deb下载libmysqlclient18/5.6.25 libmysqlclient18/5.6…

配置BGP与IGP交互和路由自动聚合示例

组网需求 如图所示&#xff0c;用户将网络划分为AS65008和AS65009&#xff0c;在AS65009内&#xff0c;使用IGP协议来计算路由&#xff08;该例使用OSPF做为IGP协议&#xff09;。要求实现两个AS之间的互相通信。 配置思路 采用如下的思路配置BGP与IGP交互&#xff1a; 在AR…

基于SpringBoot的健身房系统的设计与实现(源码+定制+开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

flex 布局比较容易犯的错误 出现边界超出的预想的情况

flex 布局比较容易犯的错误 出现边界超出的预想的情况 如图 当使用flex布局时&#xff0c;设置flex:1 或者是flex:x 时 如果没有多层嵌套的flex布局&#xff0c;内容超出flex&#xff1a;1规定的后&#xff0c;仍然会撑大融器 在flex:1 处设置 overflow:hidden 即可超出后不显…