消息中間件—RocketMQ消息存儲(二)

文章摘要:上篇中主要介紹了RocketMQ存儲部分的整體架構(gòu)設(shè)計,本篇將深入分析RocketMQ存儲部分的細節(jié)內(nèi)容
在本篇文章中,小編將繼續(xù)深入分析與介紹RocketMQ消息存儲部分中的關(guān)鍵技術(shù)—Mmap與PageCache、幾種RocketMQ存儲優(yōu)化技術(shù)(包括預先創(chuàng)建分配MappedFile、文件預熱和mlock系統(tǒng)調(diào)用)、RocketMQ內(nèi)部封裝類—CommitLog/MappedFile/MappedFileQueue/ConsumeQueue的簡析。然后,再簡要介紹下RocketMQ消息刷盤兩種主要方式。在讀完本篇幅后,希望讀者能夠?qū)ocketMQ消息存儲部分有一個更為深刻和全面的認識。

一、RocketMQ存儲整體設(shè)計架構(gòu)回顧

RocketMQ之所以能單機支持上萬的持久化隊列與其獨特的存儲結(jié)構(gòu)是密不可分的,這里再來看下其文件存儲的整體設(shè)計架構(gòu)。(ps:之前看了@艾瑞克的《RocketMQ高性能之底層存儲設(shè)計》覺得其表達方式和思路相當清晰,因此修改了下(一)篇中的“RocketMQ消息存儲整體架構(gòu)”)

RokcetMQ文件存儲設(shè)計架構(gòu)_v2.jpg

上面圖中假設(shè)Consumer端默認設(shè)置的是同一個ConsumerGroup,因此Consumer端線程采用的是負載訂閱的方式進行消費。從架構(gòu)圖中可以總結(jié)出如下幾個關(guān)鍵點:
(1)消息生產(chǎn)與消息消費相互分離,Producer端發(fā)送消息最終寫入的是CommitLog(消息存儲的日志數(shù)據(jù)文件),Consumer端先從ConsumeQueue(消息邏輯隊列)讀取持久化消息的起始物理位置偏移量offset、大小size和消息Tag的HashCode值,隨后再從CommitLog中進行讀取待拉取消費消息的真正實體內(nèi)容部分;
(2)RocketMQ的CommitLog文件采用混合型存儲(所有的Topic下的消息隊列共用同一個CommitLog的日志數(shù)據(jù)文件),并通過建立類似索引文件—ConsumeQueue的方式來區(qū)分不同Topic下面的不同MessageQueue的消息,同時為消費消息起到一定的緩沖作用(只有ReputMessageService異步服務(wù)線程通過doDispatch異步生成了ConsumeQueue隊列的元素后,Consumer端才能進行消費)。這樣,只要消息寫入并刷盤至CommitLog文件后,消息就不會丟失,即使ConsumeQueue中的數(shù)據(jù)丟失,也可以通過CommitLog來恢復。
(3)RocketMQ每次讀寫文件的時候真的是完全順序讀寫么?這里,發(fā)送消息時,生產(chǎn)者端的消息確實是順序?qū)懭隒ommitLog;訂閱消息時,消費者端也是順序讀取ConsumeQueue,然而根據(jù)其中的起始物理位置偏移量offset讀取消息真實內(nèi)容卻是隨機讀取CommitLog。 在RocketMQ集群整體的吞吐量、并發(fā)量非常高的情況下,隨機讀取文件帶來的性能開銷影響還是比較大的,那么這里如何去優(yōu)化和避免這個問題呢?后面的章節(jié)將會逐步來解答這個問題。
這里,同樣也可以總結(jié)下RocketMQ存儲架構(gòu)的優(yōu)缺點:
(1)優(yōu)點:
a、ConsumeQueue消息邏輯隊列較為輕量級;
b、對磁盤的訪問串行化,避免磁盤竟爭,不會因為隊列增加導致IOWAIT增高;
(2)缺點:
a、對于CommitLog來說寫入消息雖然是順序?qū)懀亲x卻變成了完全的隨機讀;
b、Consumer端訂閱消費一條消息,需要先讀ConsumeQueue,再讀Commit Log,一定程度上增加了開銷;

二、RocketMQ存儲關(guān)鍵技術(shù)—再談Mmap與PageCache

上篇中已經(jīng)對Mmap內(nèi)存映射技術(shù)(具體為JDK NIO的MappedByteBuffer)和PageCache概念進行了一定的深入分析。本節(jié)在回顧這兩種技術(shù)的同時,從其他的維度來闡述上篇未涉及的細節(jié)點。

1.1、Mmap內(nèi)存映射技術(shù)—MappedByteBuffer

(1)Mmap內(nèi)存映射技術(shù)的特點
Mmap內(nèi)存映射和普通標準IO操作的本質(zhì)區(qū)別在于它并不需要將文件中的數(shù)據(jù)先拷貝至OS的內(nèi)核IO緩沖區(qū),而是可以直接將用戶進程私有地址空間中的一塊區(qū)域與文件對象建立映射關(guān)系,這樣程序就好像可以直接從內(nèi)存中完成對文件讀/寫操作一樣。只有當缺頁中斷發(fā)生時,直接將文件從磁盤拷貝至用戶態(tài)的進程空間內(nèi),只進行了一次數(shù)據(jù)拷貝。對于容量較大的文件來說(文件大小一般需要限制在1.5~2G以下),采用Mmap的方式其讀/寫的效率和性能都非常高。

Java NIO內(nèi)存映射模型圖.jpg

(2)JDK NIO的MappedByteBuffer簡要分析
從JDK的源碼來看,MappedByteBuffer繼承自ByteBuffer,其內(nèi)部維護了一個邏輯地址變量—address。在建立映射關(guān)系時,MappedByteBuffer利用了JDK NIO的FileChannel類提供的map()方法把文件對象映射到虛擬內(nèi)存。仔細看源碼中map()方法的實現(xiàn),可以發(fā)現(xiàn)最終其通過調(diào)用native方法map0()完成文件對象的映射工作,同時使用Util.newMappedByteBuffer()方法初始化MappedByteBuffer實例,但最終返回的是DirectByteBuffer的實例。在Java程序中使用MappedByteBuffer的get()方法來獲取內(nèi)存數(shù)據(jù)是最終通過DirectByteBuffer.get()方法實現(xiàn)(底層通過unsafe.getByte()方法,以“地址 + 偏移量”的方式獲取指定映射至內(nèi)存中的數(shù)據(jù))。
(3)使用Mmap的限制
a.Mmap映射的內(nèi)存空間釋放的問題;由于映射的內(nèi)存空間本身就不屬于JVM的堆內(nèi)存區(qū)(Java Heap),因此其不受JVM GC的控制,卸載這部分內(nèi)存空間需要通過系統(tǒng)調(diào)用 unmap()方法來實現(xiàn)。然而unmap()方法是FileChannelImpl類里實現(xiàn)的私有方法,無法直接顯示調(diào)用。RocketMQ中的做法是,通過Java反射的方式調(diào)用“sun.misc”包下的Cleaner類的clean()方法來釋放映射占用的內(nèi)存空間;
b.MappedByteBuffer內(nèi)存映射大小限制;因為其占用的是虛擬內(nèi)存(非JVM的堆內(nèi)存),大小不受JVM的-Xmx參數(shù)限制,但其大小也受到OS虛擬內(nèi)存大小的限制。一般來說,一次只能映射1.5~2G 的文件至用戶態(tài)的虛擬內(nèi)存空間,這也是為何RocketMQ默認設(shè)置單個CommitLog日志數(shù)據(jù)文件為1G的原因了;
c.使用MappedByteBuffe的其他問題;會存在內(nèi)存占用率較高和文件關(guān)閉不確定性的問題;

2.2、OS的PageCache機制

PageCache是OS對文件的緩存,用于加速對文件的讀寫。一般來說,程序?qū)ξ募M行順序讀寫的速度幾乎接近于內(nèi)存的讀寫訪問,這里的主要原因就是在于OS使用PageCache機制對讀寫訪問操作進行了性能優(yōu)化,將一部分的內(nèi)存用作PageCache。
(1)對于數(shù)據(jù)文件的讀取,如果一次讀取文件時出現(xiàn)未命中PageCache的情況,OS從物理磁盤上訪問讀取文件的同時,會順序?qū)ζ渌噜弶K的數(shù)據(jù)文件進行預讀取(ps:順序讀入緊隨其后的少數(shù)幾個頁面)。這樣,只要下次訪問的文件已經(jīng)被加載至PageCache時,讀取操作的速度基本等于訪問內(nèi)存。
(2)對于數(shù)據(jù)文件的寫入,OS會先寫入至Cache內(nèi),隨后通過異步的方式由pdflush內(nèi)核線程將Cache內(nèi)的數(shù)據(jù)刷盤至物理磁盤上。
對于文件的順序讀寫操作來說,讀和寫的區(qū)域都在OS的PageCache內(nèi),此時讀寫性能接近于內(nèi)存。RocketMQ的大致做法是,將數(shù)據(jù)文件映射到OS的虛擬內(nèi)存中(通過JDK NIO的MappedByteBuffer),寫消息的時候首先寫入PageCache,并通過異步刷盤的方式將消息批量的做持久化(同時也支持同步刷盤);訂閱消費消息時(對CommitLog操作是隨機讀取),由于PageCache的局部性熱點原理且整體情況下還是從舊到新的有序讀,因此大部分情況下消息還是可以直接從Page Cache中讀取,不會產(chǎn)生太多的缺頁(Page Fault)中斷而從磁盤讀取。

RokcetMQ文件存儲PageCache機制.jpg

PageCache機制也不是完全無缺點的,當遇到OS進行臟頁回寫,內(nèi)存回收,內(nèi)存swap等情況時,就會引起較大的消息讀寫延遲。
對于這些情況,RocketMQ采用了多種優(yōu)化技術(shù),比如內(nèi)存預分配,文件預熱,mlock系統(tǒng)調(diào)用等,來保證在最大可能地發(fā)揮PageCache機制優(yōu)點的同時,盡可能地減少其缺點帶來的消息讀寫延遲。

三、RocketMQ存儲優(yōu)化技術(shù)

這一節(jié)將主要介紹RocketMQ存儲層采用的幾項優(yōu)化技術(shù)方案在一定程度上可以減少PageCache的缺點帶來的影響,主要包括內(nèi)存預分配,文件預熱和mlock系統(tǒng)調(diào)用。

3.1 預先分配MappedFile

在消息寫入過程中(調(diào)用CommitLog的putMessage()方法),CommitLog會先從MappedFileQueue隊列中獲取一個 MappedFile,如果沒有就新建一個。
這里,MappedFile的創(chuàng)建過程是將構(gòu)建好的一個AllocateRequest請求(具體做法是,將下一個文件的路徑、下下個文件的路徑、文件大小為參數(shù)封裝為AllocateRequest對象)添加至隊列中,后臺運行的AllocateMappedFileService服務(wù)線程(在Broker啟動時,該線程就會創(chuàng)建并運行),會不停地run,只要請求隊列里存在請求,就會去執(zhí)行MappedFile映射文件的創(chuàng)建和預分配工作,分配的時候有兩種策略,一種是使用Mmap的方式來構(gòu)建MappedFile實例,另外一種是從TransientStorePool堆外內(nèi)存池中獲取相應(yīng)的DirectByteBuffer來構(gòu)建MappedFile(ps:具體采用哪種策略,也與刷盤的方式有關(guān))。并且,在創(chuàng)建分配完下個MappedFile后,還會將下下個MappedFile預先創(chuàng)建并保存至請求隊列中等待下次獲取時直接返回。RocketMQ中預分配MappedFile的設(shè)計非常巧妙,下次獲取時候直接返回就可以不用等待MappedFile創(chuàng)建分配所產(chǎn)生的時間延遲。

預分配MappedFile的主要過程.jpg

3.2 文件預熱&&mlock系統(tǒng)調(diào)用

(1)mlock系統(tǒng)調(diào)用:其可以將進程使用的部分或者全部的地址空間鎖定在物理內(nèi)存中,防止其被交換到swap空間。對于RocketMQ這種的高吞吐量的分布式消息隊列來說,追求的是消息讀寫低延遲,那么肯定希望盡可能地多使用物理內(nèi)存,提高數(shù)據(jù)讀寫訪問的操作效率。
(2)文件預熱:預熱的目的主要有兩點;第一點,由于僅分配內(nèi)存并進行mlock系統(tǒng)調(diào)用后并不會為程序完全鎖定這些內(nèi)存,因為其中的分頁可能是寫時復制的。因此,就有必要對每個內(nèi)存頁面中寫入一個假的值。其中,RocketMQ是在創(chuàng)建并分配MappedFile的過程中,預先寫入一些隨機值至Mmap映射出的內(nèi)存空間里。第二,調(diào)用Mmap進行內(nèi)存映射后,OS只是建立虛擬內(nèi)存地址至物理地址的映射表,而實際并沒有加載任何文件至內(nèi)存中。程序要訪問數(shù)據(jù)時OS會檢查該部分的分頁是否已經(jīng)在內(nèi)存中,如果不在,則發(fā)出一次缺頁中斷。這里,可以想象下1G的CommitLog需要發(fā)生多少次缺頁中斷,才能使得對應(yīng)的數(shù)據(jù)才能完全加載至物理內(nèi)存中(ps:X86的Linux中一個標準頁面大小是4KB)?RocketMQ的做法是,在做Mmap內(nèi)存映射的同時進行madvise系統(tǒng)調(diào)用,目的是使OS做一次內(nèi)存映射后對應(yīng)的文件數(shù)據(jù)盡可能多的預加載至內(nèi)存中,從而達到內(nèi)存預熱的效果。

四、RocketMQ存儲相關(guān)的模型與封裝類簡析

(1)CommitLog:消息主體以及元數(shù)據(jù)的存儲主體,存儲Producer端寫入的消息主體內(nèi)容。單個文件大小默認1G ,文件名長度為20位,左邊補零,剩余為起始偏移量,比如00000000000000000000代表了第一個文件,起始偏移量為0,文件大小為1G=1073741824;當?shù)谝粋€文件寫滿了,第二個文件為00000000001073741824,起始偏移量為1073741824,以此類推。消息主要是順序?qū)懭肴罩疚募斘募M了,寫入下一個文件;
(2) ConsumeQueue:消息消費的邏輯隊列,其中包含了這個MessageQueue在CommitLog中的起始物理位置偏移量offset,消息實體內(nèi)容的大小和Message Tag的哈希值。從實際物理存儲來說,ConsumeQueue對應(yīng)每個Topic和QueuId下面的文件。單個文件大小約5.72M,每個文件由30W條數(shù)據(jù)組成,每個文件默認大小為600萬個字節(jié),當一個ConsumeQueue類型的文件寫滿了,則寫入下一個文件;
(3)IndexFile:用于為生成的索引文件提供訪問服務(wù),通過消息Key值查詢消息真正的實體內(nèi)容。在實際的物理存儲上,文件名則是以創(chuàng)建時的時間戳命名的,固定的單個IndexFile文件大小約為400M,一個IndexFile可以保存 2000W個索引;
(4)MapedFileQueue:對連續(xù)物理存儲的抽象封裝類,源碼中可以通過消息存儲的物理偏移量位置快速定位該offset所在MappedFile(具體物理存儲位置的抽象)、創(chuàng)建、刪除MappedFile等操作;
(5)MappedFile:文件存儲的直接內(nèi)存映射業(yè)務(wù)抽象封裝類,源碼中通過操作該類,可以把消息字節(jié)寫入PageCache緩存區(qū)(commit),或者原子性地將消息持久化的刷盤(flush);

五、RocketMQ消息刷盤的主要過程

在RocketMQ中消息刷盤主要可以分為同步刷盤和異步刷盤兩種。

RocketMQ同步&&異步刷盤兩種方式.jpg

(1)同步刷盤:如上圖所示,只有在消息真正持久化至磁盤后,RocketMQ的Broker端才會真正地返回給Producer端一個成功的ACK響應(yīng)。同步刷盤對MQ消息可靠性來說是一種不錯的保障,但是性能上會有較大影響,一般適用于金融業(yè)務(wù)應(yīng)用領(lǐng)域。RocketMQ同步刷盤的大致做法是,基于生產(chǎn)者消費者模型,主線程創(chuàng)建刷盤請求實例—GroupCommitRequest并在放入刷盤寫隊列后喚醒同步刷盤線程—GroupCommitService,來執(zhí)行刷盤動作(其中用了CAS變量和CountDownLatch來保證線程間的同步)。這里,RocketMQ源碼中用讀寫雙緩存隊列(requestsWrite/requestsRead)來實現(xiàn)讀寫分離,其帶來的好處在于內(nèi)部消費生成的同步刷盤請求可以不用加鎖,提高并發(fā)度。
(2)異步刷盤:能夠充分利用OS的PageCache的優(yōu)勢,只要消息寫入PageCache即可將成功的ACK返回給Producer端。消息刷盤采用后臺異步線程提交的方式進行,降低了讀寫延遲,提高了MQ的性能和吞吐量。異步和同步刷盤的區(qū)別在于,異步刷盤時,主線程并不會阻塞,在將刷盤線程wakeup后,就會繼續(xù)執(zhí)行。

六、結(jié)語

在參考了@艾瑞克的那篇RocketMQ存儲相關(guān)技術(shù)博文后,讓我理解了公眾號的文章與其他技術(shù)細節(jié)文章應(yīng)該是有所區(qū)別的,公眾號文章還是力求精簡(ps:貼大量代碼尤其需要慎重),篇幅太長會影響閱讀體驗,更多的內(nèi)容應(yīng)該以各種設(shè)計圖和少量的文字為說明。同時,由于RocketMQ本身較為復雜,光看技術(shù)文章只能理解和領(lǐng)會一個大概,更多地還是需要自己多擼源碼、Debug以及多實踐才能對其有一個較為深入的理解。
由于目前微信對本公眾號依然沒有放開評論功能,需要討論的同學可以直接在公號內(nèi)給我留言,我會依次回復內(nèi)容。如果喜歡本文,請收藏后點個贊并轉(zhuǎn)發(fā)朋友圈哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 文章摘要:MQ分布式消息隊列大致流程在于消息的一發(fā)一收一存,本篇將為大家主要介紹下RocketMQ存儲部分的架構(gòu)消...
    癲狂俠閱讀 38,269評論 9 57
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,993評論 2 89
  • RocketMQ是一款分布式、隊列模型的消息中間件,具有以下特點: 能夠保證嚴格的消息順序 提供豐富的消息拉取模式...
    AI喬治閱讀 2,086評論 2 5
  • 0.前言 RMQ對于消息持久化的方式是順序?qū)懙奖镜卮疟P文件,相對于持久化到遠程的數(shù)據(jù)庫或者KV來說,往本地磁盤文件...
    lambdacalculus閱讀 2,702評論 1 6
  • 第一次想把出來看到的東西系統(tǒng)的寫出來,就這樣隨便寫寫吧。 # 有關(guān)閱讀 讀萬卷書,行萬里路,一直是我追求...
    香蕉的繼承人閱讀 248評論 0 0