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

王子發表於2020-09-24

 

前言

上篇文章王子和大家討論了一下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生產者傳送訊息的底層原理

 

相關文章