深入研究Broker是如何持久化的

 

前言

上篇文章王子和大家討論了一下RocketMQ生產者發送消息的底層原理,今天我們接着這個話題,繼續深入聊一聊RocketMQ的Broker是如何持久化的。

Broker的持久化對於整個RocketMQ的運行起着至關重要的作用,爲什麼這麼說呢?

其實解釋起來很容易,因爲消息中間件要實現的功能不僅僅是消息的發送和接收,它本身還要有很強大的存儲能力,把來自各個系統的消息持久化到磁盤上。

只有這樣,在其他系統消費消息時才能從磁盤中讀取想要的消息。

如果不持久化到磁盤上,而是通過內存存儲消息,一是內存無法存儲大量的消息,二是出現故障消息將會丟失。

所以,Broker的持久化是比較核心的機制,它決定了MQ消息吞吐量,和保證消息的可靠性。

今天我們就來聊一聊,Broker是如何持久化的。

 

CommitLog

首先我們思考一下,當Broker接收到生產者發來的消息後,內部會做些什麼呢?

這時候我們就引入了一個新的概念CommitLog,它就是一個日誌文件。Broker接收到消息後的第一步就是把消息寫到這個日誌文件中,而且是順序寫入的。

那麼CommitLog文件是怎麼存儲的呢?

它可不是直接一個日誌文件進行存儲的,而是分成很多份存儲在磁盤中,每一份限定了最大1GB。

當Broker接收到新的消息時就會順序的追加到日誌文件的末尾,而當文件大小到了1GB,就會新創建一個日誌文件,新的消息就會寫入新的日誌文件,循環往復。

 

MessageQueue是如何存儲的

剛纔我們說了,Broker接收到消息要把消息存儲到日誌文件中,那麼上篇文章我們講的MessageQueue又是如何存儲的呢?

這時候我們就又引出了一個新的概念,ConsumeQueue文件,每個MessageQueue會存儲到多個ConsumeQueue文件中。

再給大家更詳細的說一下,其實Broker的磁盤上是有類似$HOME/store/consumequeue/{topic}/{queueid}/{filename}這樣的目錄的:

這裏面的{topic}代表的就是我們聲明的Topic,{queueid}代表的就是我們單個的MessageQueue,而{filename}就是我們的存儲文件多個ConsumeQueue文件了。

在ConsumeQueue文件中其實存儲的是一條消息對應在CommitLog中的offset(偏移量)。

這麼說可能小夥伴們不太理解,別急,王子接下來畫個圖跟大家仔細的聊一聊。

 

 

現在我們假設生產者發送了一個消息到一個Topic中,這個Topic的名字叫orderTopic,並指定了它有兩個MessageQueue:MessageQueue1、MessageQueue2。

那麼Broker接收到消息後,首先把數據存放到了CommitLog中。

然後每個MessageQueue對應了一個ConsumeQueue(實際可以是多個),對應的是ConsumeQueue1、ConsumeQueue2。

那麼現在在Broker的磁盤上就有了兩個路徑文件:

$HOME/store/consumequeue/orderTopic/MessageQueue1/ConsumeQueue1

$HOME/store/consumequeue/orderTopic/MessageQueue2/ConsumeQueue2

然後呢,在寫入CommitLog文件後,會同時將消息在CommitLog文件中的位置(偏移量offset)寫入到對應的ConsumeQueue中。

所以,ConsumeQueue中存儲的其實是消息的引用地址,同時還會存儲消息的tag、hashcode以及消息的長度。每條數據20個字節進行存儲。

當我們獲取消息的時候就可以通過ConsumeQueue中的引用地址去CommitLog中找到我們想要的消息了。

這樣解釋起來相信小夥伴們應該可以明白了吧。

 

寫入CommitLog的性能探索

接下來我們聊下一個話題,當Broker獲取到消息寫入CommitLog中時,是如何保證寫入性能的呢?

爲什麼要優化寫入的性能呢?因爲這一步驟的寫入性能直接影響着Broker的吞吐量。

如果每次寫入消息速度很慢,那麼每秒能處理的消息數量自然就會跟着大大減少了,這個相信小夥伴們都可以理解。

那麼RocketMQ針對這一步驟是怎麼做的呢?

實際上,它採用了OS操作系統的PageCache和順序寫兩個機制,來提升了寫入CommitLog的性能。

首先我們之前就聊過了,Broker在寫入CommitLog時,採用的是順序寫入的方式,每次只要在文件的末尾追加寫入數據就可以了,這樣的方式要比隨機寫入的方式性能提升不少。

另外,其實寫入CommitLog日誌時,並不是直接將數據寫入到磁盤文件中的,而是先寫入OS操作系統的PageCache中,然後由OS操作系統的後臺線程選擇時間,異步化的把PageCache中的數據同步到物理磁盤中的。

所以通過順序寫+寫入PageCache+異步刷盤這麼一套優化過後,其實寫入CommitLog的性能甚至可以和直接寫入內存相媲美。

也正是因爲這,才保證了Broker的高吞吐量。

 

同步刷盤和異步刷盤

剛纔我們聊到的就是異步刷盤的策略,Broker在寫入OS的PageCache之後,就直接返回給生產者ACK了。

這樣,生產者就會認爲我的消息已經成功發送給了Broker。那麼這樣的策略是否會存在問題呢?

其實簡單想一想就會明白,問題肯定是存在的。

因爲OS操作系統的PageCache也是一種緩存,如果寫入了緩存就認爲發送成功沒有問題了,那如果緩存還沒來得及刷新到物理磁盤,這個時候Broker掛掉了,會發生什麼呢?

當然,這個時候緩存中的消息數據就會丟失,無法恢復!

所以說技術的選擇上是有舍有得的,如果選擇了異步刷盤的策略,就會大大提高Broker的吞吐量,但同時也會有丟失消息的隱患。

那麼什麼是同步刷盤策略呢?

其實同步刷盤就是跳過了PageCache這一步驟,當生產者發送消息給Broker後,Broker必須把數據存到真實的物理磁盤中之後纔會返回ACK給生產者,這個時候生產者纔會斷定消息發送成功了。

消息一旦寫入物理磁盤,除非是磁盤硬件損壞,導致數據丟失。否則我們就可以認爲消息是不會丟失的了。

在同步刷盤策略下,假如沒有刷到物理磁盤上時Broker掛掉了,這時是不會返回ACK給生產者的,那麼生產者會認爲發送失敗,進行消息重發機制就可以了。

當主從切換完成後,消息就會正常的寫入Broker了。所以這種策略是可以保證消息不會丟失的

還是那句話,技術的選擇是有舍有得的,使用同步刷盤策略保證了消息的可靠性,但同時會降低Broker的吞吐量。

所以具體選擇哪種策略,還要根據實際的業務需求來定奪了。

 

總結

好了,今天王子和大家深入的聊了聊Broker是如何持久化的,介紹了什麼是CommitLog,什麼是ConsumeQueue。

又和大家聊了聊寫入CommitLog的實現細節,如何通過PageCache保證性能。

最後和大家介紹了同步刷盤、異步刷盤的不同之處。

那今天的分享就到這裏了,我們下期再見。

 

往期文章推薦:

什麼是消息中間件?主要作用是什麼?

常見的消息中間件有哪些?你們是怎麼進行技術選型的?

你懂RocketMQ 的架構原理嗎?

聊一聊RocketMQ的註冊中心NameServer

Broker的主從架構是怎麼實現的?

RocketMQ生產部署架構如何設計

RabbitMQ和Kafka的高可用集羣原理

RocketMQ的發送模式和消費模式

討論一下秒殺系統的技術難點與解決方案

秒殺系統中的扣減庫存和流量削峯

深入研究RocketMQ生產者發送消息的底層原理

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章