文章摘要:MQ分佈式消息隊列大體流程在於消息的一發一收一存,本篇將爲你們主要介紹下RocketMQ存儲部分的架構
消息存儲是MQ消息隊列中最爲複雜和最爲重要的一部分,因此小編也就放在RocketMQ系列篇幅中最後一部分來進行闡述和介紹。本文先從目前幾種比較經常使用的MQ消息隊列存儲方式出發,爲你們介紹RocketMQ選擇磁盤文件存儲的緣由。而後,本文分別從RocketMQ的消息存儲總體架構和RocketMQ文件存儲模型層次結構兩方面進行深刻分析介紹。使得你們讀完本文後對RocketMQ消息存儲部分有一個大體的瞭解和認識。
這裏先回顧往期RocketMQ技術分享的篇幅(若是有童鞋沒有讀過以前的文章,建議先好好讀下以前小編寫的篇幅或者其餘網上相關的博客,把RocketMQ消息發送和消費部分的流程先大體搞明白):
(1)消息中間件—RocketMQ的RPC通訊(一)
(2)消息中間件—RocketMQ的RPC通訊(二)
(3)消息中間件—RocketMQ消息發送
(4)消息中間件—RocketMQ消息消費(一)
(5)消息中間件—RocketMQ消息消費(二)(push模式實現)
(6)消息中間件—RocketMQ消息消費(三)(消息消費重試)linux
當前業界幾款主流的MQ消息隊列採用的存儲方式主要有如下三種方式:
(1)分佈式KV存儲:這類MQ通常會採用諸如levelDB、RocksDB和Redis來做爲消息持久化的方式,因爲分佈式緩存的讀寫能力要優於DB,因此在對消息的讀寫能力要求都不是比較高的狀況下,採用這種方式倒也不失爲一種能夠替代的設計方案。消息存儲於分佈式KV須要解決的問題在於如何保證MQ總體的可靠性?
(2)文件系統:目前業界較爲經常使用的幾款產品(RocketMQ/Kafka/RabbitMQ)均採用的是消息刷盤至所部署虛擬機/物理機的文件系統來作持久化(刷盤通常能夠分爲異步刷盤和同步刷盤兩種模式)。小編認爲,消息刷盤爲消息存儲提供了一種高效率、高可靠性和高性能的數據持久化方式。除非部署MQ機器自己或是本地磁盤掛了,不然通常是不會出現沒法持久化的故障問題。
(3)關係型數據庫DB:Apache下開源的另一款MQ—ActiveMQ(默認採用的KahaDB作消息存儲)可選用JDBC的方式來作消息持久化,經過簡單的xml配置信息便可實現JDBC消息存儲。因爲,普通關係型數據庫(如Mysql)在單表數據量達到千萬級別的狀況下,其IO讀寫性能每每會出現瓶頸。所以,若是要選型或者自研一款性能強勁、吞吐量大、消息堆積能力突出的MQ消息隊列,那麼小編並不推薦採用關係型數據庫做爲消息持久化的方案。在可靠性方面,該種方案很是依賴DB,若是一旦DB出現故障,則MQ的消息就沒法落盤存儲會致使線上故障;
所以,綜合上所述從存儲效率來講, 文件系統>分佈式KV存儲>關係型數據庫DB,直接操做文件系統確定是最快和最高效的,而關係型數據庫TPS通常相比於分佈式KV系統會更低一些(簡略地說,關係型數據庫自己也是一個須要讀寫文件server,這時MQ做爲client與其創建鏈接併發送待持久化的消息數據,同時又須要依賴DB的事務等,這一系列操做都比較消耗性能),因此若是追求高效的IO讀寫,那麼選擇操做文件系統會更加合適一些。可是若是從易於實現和快速集成來看,關係型數據庫DB>分佈式KV存儲>文件系統,可是性能會降低不少。
另外,從消息中間件的自己定義來考慮,應該儘可能減小對於外部第三方中間件的依賴。通常來講依賴的外部系統越多,也會使得自己的設計越複雜,因此小編我的的理解是採用文件系統做爲消息存儲的方式,更貼近消息中間件自己的定義。算法
RokcetMQ存儲設計架構.jpgsql
上圖即爲RocketMQ的消息存儲總體架構,RocketMQ採用的是混合型的存儲結構,即爲Broker單個實例下全部的隊列共用一個日誌數據文件(即爲CommitLog)來存儲。而Kafka採用的是獨立型的存儲結構,每一個隊列一個文件。這裏小編認爲,RocketMQ採用混合型存儲結構的缺點在於,會存在較多的隨機讀操做,所以讀的效率偏低。同時消費消息須要依賴ConsumeQueue,構建該邏輯消費隊列須要必定開銷。數據庫
從上面的總體架構圖中可見,RocketMQ的混合型存儲結構針對Producer和Consumer分別採用了數據和索引部分相分離的存儲結構,Producer發送消息至Broker端,而後Broker端使用同步或者異步的方式對消息刷盤持久化,保存至CommitLog中。只要消息被刷盤持久化至磁盤文件CommitLog中,那麼Producer發送的消息就不會丟失。正由於如此,Consumer也就確定有機會去消費這條消息,至於消費的時間能夠稍微滯後一些也沒有太大的關係。退一步地講,即便Consumer端第一次無法拉取到待消費的消息,Broker服務端也可以經過長輪詢機制等待必定時間延遲後再次發起拉取消息的請求。
這裏,RocketMQ的具體作法是,使用Broker端的後臺服務線程—ReputMessageService不停地分發請求並異步構建ConsumeQueue(邏輯消費隊列)和IndexFile(索引文件)數據(ps:對於該服務線程在消息消費篇幅也有過介紹,不清楚的童鞋能夠跳至消息消費篇幅再理解下)。而後,Consumer便可根據ConsumerQueue來查找待消費的消息了。其中,ConsumeQueue(邏輯消費隊列)做爲消費消息的索引,保存了指定Topic下的隊列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。而IndexFile(索引文件)則只是爲了消息查詢提供了一種經過key或時間區間來查詢消息的方法(ps:這種經過IndexFile來查找消息的方法不影響發送與消費消息的主流程)。緩存
這裏有必要先稍微簡單地介紹下page cache的概念。系統的全部文件I/O請求,操做系統都是經過page cache機制實現的。對於操做系統來講,磁盤文件都是由一系列的數據塊順序組成,數據塊的大小由操做系統自己而決定,x86的linux中一個標準頁面大小是4KB。
操做系統內核在處理文件I/O請求時,首先到page cache中查找(page cache中的每個數據塊都設置了文件以及偏移量地址信息),若是未命中,則啓動磁盤I/O,將磁盤文件中的數據塊加載到page cache中的一個空閒塊,而後再copy到用戶緩衝區中。
page cache自己也會對數據文件進行預讀取,對於每一個文件的第一個讀請求操做,系統在讀入所請求頁面的同時會讀入緊隨其後的少數幾個頁面。所以,想要提升page cache的命中率(儘可能讓訪問的頁在物理內存中),從硬件的角度來講確定是物理內存越大越好。從操做系統層面來講,訪問page cache時,即便只訪問1k的消息,系統也會提早預讀取更多的數據,在下次讀取消息時, 就極可能能夠命中內存。
在RocketMQ中,ConsumeQueue邏輯消費隊列存儲的數據較少,而且是順序讀取,在page cache機制的預讀取做用下,Consume Queue的讀性能會比較高近乎內存,即便在有消息堆積狀況下也不會影響性能。而對於CommitLog消息存儲的日誌數據文件來講,讀取消息內容時候會產生較多的隨機訪問讀取,嚴重影響性能。若是選擇合適的系統IO調度算法,好比設置調度算法爲「Noop」(此時塊存儲採用SSD的話),隨機讀的性能也會有所提高。
另外,RocketMQ主要經過MappedByteBuffer對文件進行讀寫操做。其中,利用了NIO中的FileChannel模型直接將磁盤上的物理文件直接映射到用戶態的內存地址中(這種Mmap的方式減小了傳統IO將磁盤文件數據在操做系統內核地址空間的緩衝區和用戶應用程序地址空間的緩衝區之間來回進行拷貝的性能開銷),將對文件的操做轉化爲直接對內存地址進行操做,從而極大地提升了文件的讀寫效率(這裏須要注意的是,採用MappedByteBuffer這種內存映射的方式有幾個限制,其中之一是一次只能映射1.5~2G 的文件至用戶態的虛擬內存,這也是爲什麼RocketMQ默認設置單個CommitLog日誌數據文件爲1G的緣由了)。服務器
RocketMQ文件存儲模型結構.jpg架構
RocketMQ文件存儲模型層次結構如上圖所示,根據類別和做用從概念模型上大體能夠劃分爲5層,下面將從各個層次分別進行分析和闡述:
(1)RocketMQ業務處理器層:Broker端對消息進行讀取和寫入的業務邏輯入口,這一層主要包含了業務邏輯相關處理操做(根據解析RemotingCommand中的RequestCode來區分具體的業務操做類型,進而執行不一樣的業務處理流程),好比前置的檢查和校驗步驟、構造MessageExtBrokerInner對象、decode反序列化、構造Response返回對象等;
(2)RocketMQ數據存儲組件層;該層主要是RocketMQ的存儲核心類—DefaultMessageStore,其爲RocketMQ消息數據文件的訪問入口,經過該類的「putMessage()」和「getMessage()」方法完成對CommitLog消息存儲的日誌數據文件進行讀寫操做(具體的讀寫訪問操做仍是依賴下一層中CommitLog對象模型提供的方法);另外,在該組件初始化時候,還會啓動不少存儲相關的後臺服務線程,包括AllocateMappedFileService(MappedFile預分配服務線程)、ReputMessageService(回放存儲消息服務線程)、HAService(Broker主從同步高可用服務線程)、StoreStatsService(消息存儲統計服務線程)、IndexService(索引文件服務線程)等;
(3)RocketMQ存儲邏輯對象層:該層主要包含了RocketMQ數據文件存儲直接相關的三個模型類IndexFile、ConsumerQueue和CommitLog。IndexFile爲索引數據文件提供訪問服務,ConsumerQueue爲邏輯消息隊列提供訪問服務,CommitLog則爲消息存儲的日誌數據文件提供訪問服務。這三個模型類也是構成了RocketMQ存儲層的總體結構(對於這三個模型類的深刻分析將放在後續篇幅中);
(4)封裝的文件內存映射層:RocketMQ主要採用JDK NIO中的MappedByteBuffer和FileChannel兩種方式完成數據文件的讀寫。其中,採用MappedByteBuffer這種內存映射磁盤文件的方式完成對大文件的讀寫,在RocketMQ中將該類封裝成MappedFile類。這裏限制的問題在上面已經講過;對於每類大文件(IndexFile/ConsumerQueue/CommitLog),在存儲時分隔成多個固定大小的文件(單個IndexFile文件大小約爲400M、單個ConsumerQueue文件大小約5.72M、單個CommitLog文件大小爲1G),其中每一個分隔文件的文件名爲前面全部文件的字節大小數+1,即爲文件的起始偏移量,從而實現了整個大文件的串聯。這裏,每一種類的單個文件均由MappedFile類提供讀寫操做服務(其中,MappedFile類提供了順序寫/隨機讀、內存數據刷盤、內存清理等和文件相關的服務);
(5)磁盤存儲層:主要指的是部署RocketMQ服務器所用的磁盤。這裏,須要考慮不一樣磁盤類型(如SSD或者普通的HDD)特性以及磁盤的性能參數(如IOPS、吞吐量和訪問時延等指標)對順序寫/隨機讀操做帶來的影響(ps:小編建議在正式業務上線以前作好多輪的性能壓測,具體用壓測的結果來評測);併發
RocketMQ的RocketMQ消息存儲(一)篇幅就先分析到這兒了。RocketMQ消息存儲部分的內容與其餘全部篇幅(RocketMQ的Remoting通訊、普通消息發送和消息消費部分)相比是最爲複雜的,須要讀者反覆多看源碼並屢次對消息讀和寫進行Debug(能夠經過在Broker端的SendMessageProcessor/PullMessageProcesssor/QueryMessaageProcessor幾個業務處理器入口,在其重要方法中打印相關重要屬性值的方式或者一步步地Debug代碼,來仔細研究下其中的存儲過程),反覆幾回後才能夠對消息存儲這部分有一個較爲深入的理解,同時也有助於提升對RocketMQ的總體理解。限於筆者的才疏學淺,對本文內容可能還有理解不到位的地方,若有闡述不合理之處還望留言一塊兒探討。app
做者:癲狂俠
連接:https://www.jianshu.com/p/b73fdd893f98
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。異步