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/
路径下包含 queues
、msg_store_persistent
、msg_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
和 lazy
。lazy
模式即为惰性队列的模式,可以通过调用 channel.queueDeclare
方法的时候在参数 x-queue-mode
中设置,也可以通过 Policy
的方式设置,如果一个队列同时使用这两种方式设置,那么 Policy
的方式具备更高的优先级。
发送消息时,惰性队列性能比普通队列好,出现性能偏差的原因是普通队列会由于内存不足而不得不将消息换页至磁盘。如果有消费者消费时,惰性队列会耗费将近 40MB 的空间来发送消息。
如果要将普通队列转变为惰性队列,那么我们需要忍受同样的性能损耗,首先需要将缓存中的消息换页至磁盘中,然后才能接收新的消息。反之,当将一个惰性队列转变为普通队列的时候,和恢复一个队列执行同样的操作,会将磁盘中的消息批量地导入到内存中。
相关文章:

RabbitMQ 存储机制
一、消息存储机制 不管是持久化的消息还是非持久化的消息都可以被写入到磁盘。持久化的消息在到达队列时就被写入到磁盘,非持久化的消息一般只保存在内存中,在内存吃紧的时候会被换入到磁盘中,以节省内存空间。这两种类型的消息的落盘处理都…...
【Java SE】类型转换
类型转换是将一个值从一种类型转换为另一种类型的过程。该过程如果从低精度数据类型转为高精度数据类型,则不会发生溢出并且总能成功,如果从高精度数据类型转为低精度数据类型,则会有信息丢失且可能失败。类型转换又可分为隐式转换和显式转换…...

JAVA:常见 JSON 库的技术详解
1、简述 在现代应用开发中,JSON(JavaScript Object Notation)已成为数据交换的标准格式。Java 提供了多种方式将对象转换为 JSON 或从 JSON 转换为对象,常见的库包括 Jackson、Gson 和 org.json。本文将介绍几种常用的 JSON 处理…...
Redis缓存击穿、雪崩、穿透解决方案
Redis 缓存击穿、雪崩、穿透解决方案 1、首先看看逻辑方面是否还有优化空间,正常流程查询redis中获取不到数据,则去数据库获取,但数据库查询并返回时,调用异步方法,将该数据存储进redis中,并设置一个较短的…...

C++ 优先算法——盛最多水的容器(双指针)
目录 题目:盛最多水的容器 1. 题目解析 2. 算法原理 3. 代码实现 题目:盛最多水的容器 1. 题目解析 题目截图: 如图所示: 水的高度一定是由较低的那条线的高度决定的:例1图中,是由7决定的,然后求出…...

blender 小车建模 建模 学习笔记
一、学习blender视频教程链接 案例4:狂奔的小车_建模_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Bt4y1E7qn?p14&spm_id_from333.788.videopod.episodes&vd_sourced0ea58f1127eed138a4ba5421c577eb1 二、开始建模 (1)创…...
导出列表数据到Excel并下载
Java导出查询到的数据列表为Excel并下载 1.背景 工作中经常有需求,需要把列表的数据导出为Excel并下载。EasyExcel工具可以很好的实现这一需求。 2.实现流程 1.引入EasyExcel依赖包 <dependency><groupId>com.alibaba</groupId><artifactId…...

基于NVIDIA NIM平台实现盲人过马路的demo(一)
前言:利用NVIDIA NIM平台提供的大模型进行编辑,通过llama-3.2-90b-vision-instruct模型进行初步的图片检测 step1: 部署大模型到本地,引用所需要的库 import os import requests import base64 import cv2 import time from datetime import datetimestep2: 观看官方使用文…...

美格智能5G车规级通信模组:以连接+算力驱动智能化进阶
2023年3月,基于高通公司第二代骁龙汽车5G调制解调器及射频系统平台SA522M/SA525M,美格智能在德国纽伦堡嵌入式系统展上正式发布全新一代5G车规级C-V2X通信模组MA922系列,迅速引起行业和市场关注。随着5G高速网联逐步成为智能汽车标配…...

[MRCTF2020]PYWebsite1
如果输入的密钥是对的那么我们就直接跳转到flag.php页面 那么我们直接访问😎,他不带我们去我们自己去. 那就用XFF呗. 知识点: 定义:X-Forwarded-For是一个HTTP请求头字段,用于识别通过HTTP代理或负载均衡方式连接到W…...
无源元器件-磁珠选型参数总结
🏡《总目录》 目录 1,概述2,磁珠选型参数2.1,电学参数2.1.3,阻抗(Impedance)2.1.2,额定电流(Rated Current)2.1.3,直流电阻(DC Resistance)2.2,机械性能参数2.2.1,外观和尺寸(Appearance and Dimensions)2.2.2,粘接强度( Bonding Strength)2.2.3,弯曲强度…...

宝顶白芽,慢生活的味觉盛宴
在快节奏的生活中,人们愈发向往那种悠然自得、返璞归真的生活方式。白茶,以其独特的韵味和清雅的风格,成为了现代人追求心灵宁静与生活品质的象征。而在众多白茶之中,竹叶青茶业出品的宝顶白芽以其甘甜醇爽的特质,成为…...
已知三角形三边长求面积用仓颉语言作答
仓颉语言 https://cangjie-lang.cn/ linux和win和mac均有sdk,在本机deepinlinuxv23下载到本地解压缩到目录下设置环境变量 source envsetup.sh 比java方便太多了,java每次都是要自己搞很久,当然,打开看一下envsertup.sh,和我们…...
【JavaScript】匿名函数及回调函数总结
JavaScript 匿名函数 匿名函数没有显式的名称, 被视为一个函数表达式,可以在不需要额外命名的情况下进行定义和使用, 通常被用作回调函数, 即将函数作为参数传递给其他函数。 回调函数是在特定事件或条件发生时被调用的函数,回调函数通常用于异步编程中…...

HTML鼠标移动的波浪线动画——页面将会初始化一个Canvas元素,并使用JavaScript代码在Canvas上绘制响应鼠标移动的波浪线动画
代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Wave Animation</title><style&…...

树莓派开发相关知识八-其他传感器
1、蜂鸣器 #!/usr/bin/env python #coding:utf-8import RPi.GPIO as GPIO import time OUT5 def init():GPIO.setwarnings(False)GPIO.setmode(GPIO.BCM)GPIO.setup(OUT,GPIO.OUT)#蜂鸣器鸣叫函数 def beep(seconds):GPIO.output(OUT,GPIO.HIGH)time.sleep(seconds)GPIO.output…...

ComfyUI - ComfyUI 工作流中集成 SAM2 + GroundingDINO 处理图像与视频 教程
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/143359538 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 SAM2 与…...

STM32G4 双ADC模式之常规同步模式独立注入模式
目录 概述 1 认识双ADC模式 2 功能实现 2.1 原理介绍 2.2 实现方法 概述 本文主要介绍STM32G4 双ADC模式之常规同步模式&独立注入模式相关内容,包括ADC模块的功能介绍,实现框架结构,以及常规同步模式&独立注入模式ADC的转换的实…...
深入理解网络协议:OSPF、VLAN、NAT与ACL详解
OSPF工作过程与基础配置 一、OSPF的工作过程 OSPF(开放最短路径优先)是一个广泛使用的路由协议,它的工作过程可以总结为以下几个步骤: 启动与邻居发现 OSPF在配置完成后,会通过本地组播地址224.0.0.5发送HELLO包。HE…...

idea 配置tomcat 服务
选择tomcat的安装路径 选到bin的文件夹的上一层就行...

.net core 接口,动态接收各类型请求的参数
[HttpPost] public async Task<IActionResult> testpost([FromForm] object info) { //Postman工具测试结果: //FromBody,Postman的body只有rawjson时才进的来 //参数为空时,Body(form-data、x-www-form-urlencoded)解析到的数据也有所…...

关注!这些型号SSD有Windows蓝屏问题需要修复
近期,在闪迪官方有一个SSD FW升级提醒,主要是为了解决Windows 11 24H2系统蓝屏的问题: Fix问题:这些SSD的主机内存缓冲区(Host Memory Buffer,简称HMB)功能可能会导致系统出现蓝屏死机ÿ…...
go语言gin框架平滑关闭——思悟项目技术2
目录 前言 直接关闭的缺陷 平滑关闭的使用场景 例子 思悟项目: golang qq邮件发送验证码——思悟项目技术1 前言 平滑关闭(graceful shutdown)是指在停止服务时,能够让现有的连接、任务或者操作优雅地完成,而不是…...

K8S flannel网络模式对比
K8S flannel网络模式对比 VXLAN 模式Host-GW 模式如何查看 Flannel 的网络模式?如何修改 Flannel 的网络模式?如何修改flannel vxlan端口?Flannel 是一个 Kubernetes 中常用的网络插件,用于在集群中的节点之间提供网络连接。Flannel 提供了多种后端实现方式,vxlan 和 host…...
Vue前端框架:Vue前端项目文件目录
文章目录 package.json 文件node_modulessrc(Source Code 的缩写)文件夹主要子文件夹及内容 publicdist package.json 文件 所在文件夹(通常是项目根目录) 虽然 package.json 本身不是一个文件夹,但它所在的文件夹&a…...
git回滚到指定的提交
如果你想回滚到特定的提交(例如 aa0ca72c),并且丢弃之后的所有更改,可以使用 git reset 命令。请注意,git reset 会改变你的提交历史,所以在多人协作项目中应谨慎使用。如果已经推送到远程仓库,…...

手机怎么玩森林之子?远程玩森林之子教程
你喜欢《森林之子》这款开放世界恐怖生存模拟游戏吗?玩家会被派到一座孤岛上,寻找一位失踪的亿万富翁,并深陷被食人生物占领的地方。你需要制作工具和武器、建造房屋,倾尽全力生存下去,无论独自一人还是与朋友一起。如…...

深度学习之网络与计算
1 网络操作与计算 1.1 前向传播与反向传播? 神经网络的计算主要有两种:前向传播(foward propagation, FP)作用于每一层的输入,通过逐层计算得到输出结果;反向传播(backward propagation, BP&a…...
《JVM第1课》Java 跨平台原理
无痛快速学习JVM,欢迎订阅本免费专栏 JVM Java的特性就是程序员一次编写,到处运行,意思是我们只需要写一份代码,就可以在不同的操作系统(windows、Linux、Mac OS等)中运行。但是不同的操作系统能看懂的指令…...

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-30
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-30 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-30目录1. Step Guided Reasoning: Improving Mathematical Reasoning using Guidance Generation and Step Reasoning摘要研究背…...