前言
上篇文章王子和大家討論了一下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保證效能。
最後和大家介紹了同步刷盤、非同步刷盤的不同之處。
那今天的分享就到這裡了,我們下期再見。
往期文章推薦: