深度剖析 TDMQ RabbitMQ 版經典隊列底層存儲機制

導語

RabbitMQ 作為開源消息隊列的標桿產品,憑借靈活的路由機制與高可用設計,支撐著海量業務場景的消息流轉。而經典隊列(Classic Queue) 作為 RabbitMQ 最基礎、應用最廣泛的隊列類型,其底層存儲機制直接決定了消息處理的性能邊界與可用性上限。

理解經典隊列的存儲架構,不僅是掌握 RabbitMQ 核心原理的關鍵,更為生產環境的運維優化提供了理論支撐。本文將從文件目錄結構、存儲格式定義、讀寫流程到運維實踐策略,全面解析經典隊列的底層存儲實現邏輯,幫助讀者深入理解其在消息生命周期管理中的核心作用。

經典隊列介紹

RabbitMQ 作為一款歷史悠久的開源消息隊列,被廣泛應用于各個領域。在 RabbitMQ 中,用戶使用虛擬主機(Vhost)隔離資源,交換機負責路由消息,隊列則是消息存儲的最小單元。

用戶通過客戶端與 RabbitMQ 的服務端建立連接后,基于通道(Channel)實現消息的高效交互:生產者經過通道將消息發送至交換機,由交換機按綁定規則路由至目標隊列;消費者則通過通道從隊列中拉取消息,完成業務邏輯處理。

在這一過程中,隊列作為消息生命周期的核心載體,衍生出三種差異化實現:

  • 經典隊列(Classic Queue):采用輕量級索引與共享存儲架構,在單機性能與存儲效率間取得平衡,適用于高吞吐非強一致性場景;

  • 仲裁隊列(Quorum Queue):基于 Raft 協議實現多副本強一致性,保障關鍵業務數據不丟失,適用于金融交易、訂單管理等關鍵業務;

  • 流隊列(Stream Queue):以日志結構存儲消息流,支持回溯消費與持久化流處理,適用于實時數據分析場景。

經典隊列作為使用頻率最高的隊列,了解它的存儲機制對于理解其可用性和性能至關重要,接下來將從存儲架構、文件格式、讀寫流程等維度,深入解析經典隊列的底層實現邏輯。

存儲架構解析

目錄結構

RabbitMQ 通過虛擬主機(Vhost)實現資源隔離,每個 Vhost 有獨立的物理存儲目錄,其典型結構如下:

vhost_name/
├── msg_store_persistent/      # 共享存儲目錄,存儲大消息
│   ├── 0.rdq                  # 共享存儲文件
│   └── 1.rdq                  # 支持文件滾動
└── queues/                    # 隊列專屬存儲目錄└── queue_name/            # 單個隊列目錄├── queue_name.qi      # 隊列索引文件└── queue_name.qs      # 隊列存儲文件

msg_store_* 是共享存儲目錄,顧名思義是這個 Vhost 下所有隊列共享的存儲。由于 Exchange 可能會將同一條消息路由到不同的隊列,而將同一條消息存儲多次會增加磁盤空間占用,因此經典隊列會將大小超過某個閾值的消息存儲在共享存儲下,通過引用計數來管理這部分消息。

每個隊列在 queues 目錄下都有屬于自己的目錄,隊列目錄下主要有兩類文件:

  • 隊列存儲:名稱為 *.qs 的文件,負責存儲這個隊列中消息大小小于這個閾值的消息。

  • 隊列索引:名稱為 *.qi 的文件,負責存儲消息元數據和消息所在位置。隊列索引存儲了消息的偏移或唯一標識,通過它們可以定位到消息在隊列存儲或共享存儲中的位置,索引文件中的 Entry 和存儲文件中的 Entry 因此在邏輯上構成了一對一的映射關系。

隊列索引

隊列索引文件由一個 Header 和若干 Entry 組成,Entry 的數量由 classic_queue_index_v2_segment_entry_count 這一參數控制,默認為4096。Entry 有兩種類型:Publish Entry 和 Ack Entry。

生產者將消息成功發送到隊列后會產生一個 Publish Entry,隊列將這條消息投遞給消費者并且得到消費者確認后會使用 Ack Entry 覆蓋原來的 Publish Entry,代表這條消息可以被刪除。

Publish Entry 存儲了這條消息的元數據,包括 MsgId、SeqId、存儲位置、消息屬性和是否持久化的標識。

MsgId 是 RabbitMQ 為每條消息隨機生成的 GUID,用來確定消息在共享存儲的位置。

SeqId 是這條消息在隊列中的序號,用來決定消息在隊列索引和隊列存儲中的位置。

隊列存儲

隊列存儲文件和索引文件是一對一的關系,當隊列刪除它的索引文件時,也會刪除對應的存儲文件。隊列存儲文件的結構與索引文件類似,也是由 Header 和 Entry 構成。Header 和 Entry 的具體組成如下所示。

共享存儲

ETS 是 Erlang 內置的單機 KV 存儲,共享存儲使用 ETS 維護了兩個組件:

  • Index:是 MsgId 到消息位置的映射。

  • FileSummary:文件到文件統計信息的映射。

經典隊列在讀取消息時通過索引文件中的 Publish Entry 獲取到 MsgId 后還需要從 Index 中獲取消息的具體位置,包括這條消息所在的文件、偏移以及它的引用計數。相同 MsgId 的多條消息只會被寫入一次,刪除消息時,它的引用計數會被減一。文件統計信息中記錄了文件中有效數據的數量,這在整理文件時會被用到。

共享存儲文件的大小由參數 msg_store_file_size_limit 控制,默認為16MB。每個文件由若干個 Entry 組成,每個 Entry 的具體組成如下所示。

核心工作流程

消息寫入

RabbitMQ 根據消息大小決定將消息寫入到哪個存儲。如果消息大小大于或等于某個值(由參數 queue_index_embed_msgs_below 控制,默認為4KB),RabbitMQ 會將其存于共享存儲中,否則會存于隊列存儲中。

將消息寫入存儲時會直接寫到內部緩沖區:

  • 隊列存儲內部的緩沖區大小由參數 classic_queue_store_v2_max_cache_size 控制,默認為512KB。

  • 共享存儲內部的緩沖區大小則固定為1MB。將消息寫入到共享存儲時除了需要寫入到緩沖區外,還需要更新它內部的 Index 和 FileSummary 組件。

緩沖區大小超過限制后會 Flush 其中的數據,值得注意的是,Flush 時不會調用 Fsync,而是調用 Write 將數據寫入到操作系統的 Page Cache 上。這種方式通過犧牲數據安全性以獲得更低的延遲,如果需要更強的數據安全性應使用仲裁隊列。

存儲寫入完成后需要在隊列索引文件中寫入 Publish Entry,此時消息被認為成功寫入了。之后還要更新內存中的消息緩存,以加速消息讀取。

消息讀取

經典隊列在內存中維護了專門的緩存來提升讀取性能,底層存儲會根據隊列的消費速率批量讀取不超過2048條消息到緩存中。讀取消息時會先檢查緩存中是否有這條消息,如果有則直接返回,否則會先將消息批量讀取到緩存。

將消息從磁盤批量讀取到內存中需要先到隊列索引中讀取元數據,然后分別到隊列存儲和共享存儲中讀取消息體,并將它們組裝到一起。即便緩存中有消息,但是實際的消息體仍然可能不在緩存中,因為過大(>12KB)過少(<10條)的消息的消息體并不會被讀到緩存里,需要在投遞消息時逐條去磁盤中讀取消息體。

文件整理

共享存儲會定時整理有效數據占比低于一半的文件以回收空間。整個過程分為三步:

  1. 將文件末尾的有效數據拷貝到文件前面的無效數據處。
  2. 更新 Index 組件。
  3. 在沒有進程讀取文件后截斷文件。

RabbitMQ 會將文件中的無效數據置0,稱為空洞(blank holes)。在文件整理時,RabbitMQ 從最后一條有效消息開始查看其是否能填補前面的空洞,如果可以就將其拷貝到前面,如果它比前面的任何一個空洞都大,那么這一次的文件整理將無法釋放任何空間,這是為了防止意外覆蓋被移動過的消息。Index 組件中存儲了消息的位置,拷貝完成后需要更新對應消息的位置。在沒有進程讀取文件后就可以截斷這個文件以節省磁盤空間。

運維實踐

發送確認

為了提高消息發送的可靠性,我們推薦用戶打開發送確認(Confirm)。RabbitMQ 會在將消息從緩沖區 Flush 到磁盤后向客戶端發送 Confirm,此時生產者可以認為這條消息已經被成功發送到隊列。

消費確認

為了提高消息消費的可靠性,我們推薦用戶打開手動確認(Manual Ack)。RabbitMQ 在收到 Ack后會寫入 Entry 到隊列索引中,只有在索引文件中的所有 Publish Entry 全部被 Ack 后,才會刪除該文件。如果消費者在發送 Ack 前宕機了,RabbitMQ 會重復投遞這條消息,確保消息能真正被消費掉。未被客戶端 Ack 的消息會堆積在內存中,如果數量過多則可能觸發內存水位限制,甚至導致服務端 OOM。因此在用戶打開手動確認后,我們建議用戶設置一次最多能預取(prefetch count)的消息數量,避免大量消息堆積在客戶端和服務端內存中。

保證隊列盡可能短

保持生產和消費速率一致可以減少消息堆積。RabbitMQ 會在發現索引文件中的消息全部被消費后刪除索引文件和對應的存儲文件,這樣可以減少磁盤空間占用。隊列的堆積數量少意味著多數讀取都可以從緩存中直接讀取到消息體,從而提升讀取性能。

總結

本文全面探討了 RabbitMQ 經典隊列的底層存儲機制,包括其整體架構、實現原理及運維實踐。經典隊列的底層存儲由隊列索引和消息存儲兩大模塊構成,其中消息存儲又細分為共享存儲和隊列存儲,通過精心設計的文件結構和內存管理策略,實現了高效的消息讀寫與存儲管理。文章詳細解析了隊列索引、消息存儲(包括共享存儲和隊列存儲)的文件結構,介紹了消息讀取與寫入的流程,以及文件整理的邏輯。在運維實踐方面,強調了發送確認、消費確認與保持隊列盡可能短的重要性,并給出了相應的配置建議。希望通過本文的介紹,可以幫助大家深入理解 RabbitMQ 經典隊列的底層存儲機制,為實際應用中的性能優化與故障排查提供有力支持。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91650.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91650.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91650.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring AI開發智能客服(Tool calling)

文章目錄前言1 思路分析2 工程結構搭建1_數據庫表2_引入依賴3_基礎代碼3 定義 Tool1_分析查詢條件2_定義Function4 系統提示詞5 配置ChatClient6 編寫Controller7 測試8 Tool calling 底層組件1_ToolCallback2_ToolDefinition3_ToolCallingManager4_ResultConverter5_ToolConte…

設計模式筆記_結構型_適配器模式

1.適配器模式介紹適配器模式是一種結構型設計模式&#xff0c;它允許不兼容的接口協同工作。適配器模式的核心思想是將一個類的接口轉換成客戶期望的另一個接口&#xff0c;使得原本由于接口不兼容而不能一起工作的類可以一起工作。你可以將其想象成一個“轉換插頭”——假設你…

事務隔離:從鎖實現到MVCC實現

文章目錄事務隔離&#xff1a;從鎖實現到MVCC實現事務四大特性事務隔離級別鎖實現概念實現事務隔離MVCC實現當前讀與快照讀實現事務隔離Read View總結事務隔離&#xff1a;從鎖實現到MVCC實現 面試的時候被面試官問到&#xff1a;你這個項目為什么使用了可重復讀而不選擇讀已提…

小架構step系列18:工具

1 概述 在寫代碼的時候&#xff0c;有很多通用的、與業務無關邏輯&#xff0c;這些一般寫成工具類方法。這些工具類方法慢慢地被積累起來&#xff0c;變成了開源包&#xff0c;可以直接使用開源包&#xff0c;而不是自己再花時間來重復造這些輪子。 這些工具類的開源包比較多…

網絡、CentOS 系統、數據庫面試知識點總結

文章目錄Linux CentOS 面試知識點整理速查復習? 一、Linux 高頻面試題? 二、MySQL 高頻面試題? 三、計算機網絡&#xff08;OSI四層模型&#xff09;高頻面試題&#x1f517; 鏈路層&#xff08;Link Layer&#xff09;&#x1f310; 網絡層&#xff08;Internet Layer&…

Vue (Official) v3.0.2 新特性 為非類npm環境引入 globalTypesPath 選項

目錄 前言 報錯信息 原因 解決方案 總結 前言 在早上更新了vscode后&#xff0c;發現自己 uni-app 項目的 .vue文件 的 template 標簽都出現了報錯。定位到了問題是因為 Vue (Official) 插件更新導致的&#xff0c;重裝了插件的上一個小版本&#xff0c;報錯消失&#xff…

程序可能的輸出

#include "csapp.h"int main() {int x 3;if (Fork() ! 0)printf("x%d\n", x);printf("x%d\n", --x);exit(0); }分析&#xff1a;父進程先執行printf("x%d\n", x); 輸出x4。后執行 printf("x%d\n", --x);輸出x3。子進程只執…

2025年UDP應用抗洪指南:從T級清洗到AI免疫,實戰防御UDP洪水攻擊

一次未防護的UDP暴露&#xff0c;可能讓日活百萬的應用瞬間癱瘓&#xff0c;損失超千萬2025年&#xff0c;隨著物聯網僵尸網絡規模指數級增長及AI驅動的自適應攻擊工具泛濫&#xff0c;UDP洪水攻擊峰值已突破8Tbps&#xff0c;單次攻擊成本卻降至50元以下。更致命的是&#xff…

centos7安裝MySQL8.4手冊

目錄前言一、首先更新插件&#xff0c;并查看當前系統版本二、安裝步驟1、創建mysql目錄2、安裝rpm包3、安裝 mysql-community-server4、啟動MySQL服務5、查看MySQL狀態6、設置開機自啟動三、查看默認密碼四、登錄mysql五、修改密碼六、開啟遠程訪問1. 修改 MySQL 配置文件2. 重…

人臉檢測算法——SCRFD

SCRFD算法核心解析 1. 算法定義與背景 SCRFD&#xff08;Sample and Computation Redistribution for Efficient Face Detection&#xff09;由Jia Guo等人于2021年在arXiv提出&#xff0c;是一種高效、高精度的人臉檢測算法&#xff0c;其核心創新在于&#xff1a; 雙重重分…

vue3+ts+elementui-表格根據相同值合并

代碼<div style"height: auto; overflow: auto"><el-table ref"dataTableRef" v-loading"loading" :data"pageData" highlight-current-row borderselection-change"handleSelectionChange" :span-method"obj…

UI前端與數字孿生融合案例:智慧城市的智慧停車引導系統

hello寶子們...我們是艾斯視覺擅長ui設計、前端開發、數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩!一、引言&#xff1a;停車難的 “城市痛點” 與數字孿生的破局之道當司機在商圈繞圈 30 分鐘仍…

java+vue+SpringBoot集團門戶網站(程序+數據庫+報告+部署教程+答辯指導)

源代碼數據庫LW文檔&#xff08;1萬字以上&#xff09;開題報告答辯稿ppt部署教程代碼講解代碼時間修改工具 技術實現 開發語言&#xff1a;后端&#xff1a;Java 前端&#xff1a;vue框架&#xff1a;springboot數據庫&#xff1a;mysql 開發工具 JDK版本&#xff1a;JDK1.8 數…

【Docker基礎】Docker-compose從入門到精通:安裝指南與核心命令全解析

目錄 前言 1 Docker-compose核心概念解析 1.1 什么是Docker-compose&#xff1f; 1.2 典型應用場景 2 Docker-compose離線安裝詳解 2.1 離線安裝背景與優勢 2.2 詳細安裝步驟 步驟1&#xff1a;獲取離線安裝包 步驟2&#xff1a;文件部署與權限設置 步驟3&#xff1a…

面試150 被圍繞的區域

思路 使用DFS&#xff0c;將所有與邊界相連的’O’都修改為‘#’,然后遍歷數組&#xff0c;如果是遇到’#‘修改為’O’,如果是’O’修改為’X’。 class Solution:def solve(self, board: List[List[str]]) -> None:"""Do not return anything, modify boar…

(數據結構)線性表(上):SeqList 順序表

線性表&#xff08;上&#xff09;&#xff1a;Seqlist 順序表基本了解線性表順序表靜態順序表動態順序表編寫動態順序表項目結構基礎結構初始化尾插頭插尾刪頭刪查找指定位置pos之前插入數據刪除指定位置pos的數據銷毀完整代碼SeqLIst.hSeqLIst.ctest.c算法題移除元素刪除有序…

WebStorm vs VSCode:前端圈的「豆腐腦甜咸之爭」

目錄 一、初識兩位主角&#xff1a;老司機與新勢力 二、開箱體驗&#xff1a;是「拎包入住」還是「毛坯房改造」 三、智能提示&#xff1a;是「知心秘書」還是「百度搜索」 四、調試功能&#xff1a;是「CT 掃描儀」還是「聽診器」 五、性能表現&#xff1a;是「重型坦克」…

C#將類屬性保存到Ini文件方法(利用拓展方法,反射方式獲取到分組名和屬性名稱屬性值)

前言&#xff1a;最近學習C#高級課程&#xff0c;里面學到了利用反射和可以得到屬性的特性、屬性名、屬性值&#xff0c;還有拓展方法&#xff0c;一直想將學到的東西利用起來&#xff0c;剛好今天在研究PropertyGrid控件時&#xff0c;想方便一點保存屬性值到配置文件&#xf…

kafka 單機部署指南(KRaft 版本)

目錄環境準備JDK安裝下載jdkjdk安裝kafka 部署kafka 下載kafka 版本號結構解析kafka 安裝下載和解壓安裝包配置 KRaft 模式格式化存儲目錄啟動kafkaKafka 配置為 systemd 服務注意事項調整 JVM 內存參數Kafka KRaft 版本&#xff08;即 Kafka 3.0 及更高版本&#xff09;使用 K…

websocket案例 599足球比分

目標地址:aHR0cHM6Ly93d3cuNTk5LmNvbS9saXZlLw接口:打開控制臺 點websocket 刷新頁面 顯示分析:不寫理論了關于websocket 幾乎發包位置都是下方圖片 不管抖音還是快手 等平臺這里在進行 new WebSocket 后 是要必須走一步的 也就是 new WebSocket().onopen() 也就是onopen 進行向…