51 精益求精:深入研究一下Broker是如何持久化存儲消息的?

目錄

 

1、爲什麼Broker數據存儲是最重要的一個環節?

2、CommitLog消息順序寫入機制

3、MessageQueue在數據存儲中是體現在哪裏呢?

4、如何讓消息寫入CommitLog文件近乎內存寫性能的?

5、同步刷盤與異步刷盤

6、對今天內容的一點小小總結


1、爲什麼Broker數據存儲是最重要的一個環節?


上次給大家分享完Producer的工作原理之後,團隊整體都對RocketMQ的數據分片機制以及發送消息的時候如何寫入各個Broker
機器有了一定的瞭解。接着小猛就開始來給大家分享最爲重要的Broker數據存儲的環節。
首先我們得明確一點,爲什麼Broker數據存儲是最重要的一個環節?
很簡單,實際上類似RocketMQ、Kafka、RabbitMQ的消息中間件系統,他們不只是讓你寫入消息和獲取消息那麼簡單,他們本身最
重要的就是提供強大的數據存儲能力,可以把億萬級的海量消息存儲在自己的服務器的磁盤上。
這樣的話,各種不同的系統從MQ中消費消息的時候,纔可以從MQ服務器的磁盤中讀取到自己需要的消息。
否則如果MQ不在機器磁盤上存儲大量的消息,如果消息都放在自己的內存裏,一個是內存很可能放不下,另外一個是可能你機器重
啓,內存裏的消息就會全部丟失了。
所以大家首先要明確一點,Broker數據存儲實際上纔是一個MQ最核心的環節,他決定了生產者消息寫入的吞吐量,決定了消息不能丟
失,決定了消費者獲取消息的吞吐量,這些都是由他決定的。
所以今天我們來深入的探索一下Broker的數據存儲機制。











2、CommitLog消息順序寫入機制


首先我們來思考一下,當生產者的消息發送到一個Broker上的時候,他接收到了一條消息,接着他會對這個消息做什麼事情?
首先第一步,他會把這個消息直接寫入磁盤上的一個日誌文件,叫做CommitLog,直接順序寫入這個文件,如下圖。



這個CommitLog是很多磁盤文件,每個文件限定最多1GB,Broker收到消息之後就直接追加寫入這個文件的末尾,就跟上面的圖裏一
樣。如果一個CommitLog寫滿了1GB,就會創建一個新的CommitLog文件。


3、MessageQueue在數據存儲中是體現在哪裏呢?


接着我們會發現一個問題,如果寫入這個Broker的消息都是進入到CommitLog中去存儲的,那麼上次我們提到的MessageQueue是體
現在哪裏的呢?
其實在Broker中,對Topic下的每個MessageQueue都會有一系列的ConsumeQueue文件。
這是什麼意思呢?
就是在Broker的磁盤上,會有下面這種格式的一系列文件:
$HOME/store/consumequeue/{topic}/{queueId}/{fileName}
上面那一串東西是什麼意思?
我們之前說過,對每個Topic你不是在這臺Broker上都會有一些MessageQueue嗎?所以你會看到,{topic}指代的就是某個Topic,
{queueId}指代的就是某個MessageQueue。
然後對存儲在這臺Broker機器上的Topic下的一個MessageQueue,他有很多的ConsumeQueue文件,這個ConsumeQueue文件裏
存儲的是一條消息對應在CommitLog文件中的offset偏移量。
很多人可能看到這裏就直接看暈了,沒明白這個是什麼意思。。。
沒關係,我們一步一圖來給大家說明一下這是怎麼回事。
首先我們假設有一個Topic,他有4個MessageQueue,然後在兩臺Broker機器上,每臺Broker機器會存儲兩個MessageQueue。
那麼此時假設生產者選擇對其中一個MessageQueue寫入了一條消息,此時消息會發送到Broker上。
然後Broker必然會把這個消息寫入自己的CommitLog文件中,是不是?
好,我們看下面的圖裏,我用紅圈畫出來了一個消息,我們假設就是剛剛寫入的消息。


















我們繼續看下面的圖,我在圖裏加入了兩個ConsumeQueue,分別叫做ConsumeQueue0和ConsumeQueue1,他們分別對應着
Topic裏的MessageQueue0和MessageQueue1。


也就是說,Topic下的MessageQueue0和MessageQueue1就放在這個Broker機器上,而且他們每個MessageQueue目前在磁盤上就
對應了一個ConsumeQueue,所以就是MessageQueue0對應着Broker磁盤上的ConsumeQueue0,MessageQueue1對應着磁盤上
的ConsumeQueue1。
接着假設Queue的名字叫做:TopicOrderPaySuccess,那麼此時在Broker磁盤上應該有如下兩個路徑的文件:
$HOME/store/consumequeue/TopicOrderPaySuccess/MessageQueue0/ConsumeQueue0磁盤文件
$HOME/store/consumequeue/TopicOrderPaySuccess/MessageQueue1/ConsumeQueue1磁盤文件
然後呢,當你的Broker收到一條消息寫入了CommitLog之後,其實他同時會將這條消息在CommitLog中的物理位置,也就是一個文
件偏移量,就是一個offset,寫入到這條消息所屬的MessageQueue對應的ConsumeQueue文件中去。
比如現在這條消息在生產者發送的時候是發送給MessageQueue0的,那麼此時Broker就會將這條消息在CommitLog中的offset偏移
量,寫入到MessageQueue0對應的ConsumeQueue0中去,如下圖所示。










所以實際上,ConsumeQueue0中存儲的是一個一個消息在CommitLog文件中的物理位置,也就是offset
所以其實大家看下面的圖,圖裏展示出來的是ConsumeQueue中的一個物理位置其實是對CommitLog文件中一個消息的引用。


實際上在ConsumeQueue中存儲的每條數據不只是消息在CommitLog中的offset偏移量,還包含了消息的長度,以及tag
hashcode,一條數據是20個字節,每個ConsumeQueue文件保存30萬條數據,大概每個文件是5.72MB。
所以實際上Topic的每個MessageQueue都對應了Broker機器上的多個ConsumeQueue文件,保存了這個MessageQueue的所有消息
在CommitLog文件中的物理位置,也就是offset偏移量。



4、如何讓消息寫入CommitLog文件近乎內存寫性能的?


接着我們給大家講一個比較關鍵的概念:對於生產者把消息寫入到Broker時,Broker會直接把消息寫入磁盤上的CommitLog文件,那
麼Broker是如何提升整個過程的性能的呢?
因爲這個部分的性能提升會直接提升Broker處理消息寫入的吞吐量,比如你寫入一條消息到CommitLog磁盤文件假設需要10ms,那
麼每個線程每秒可以處理100個寫入消息,假設有100個線程,每秒只能處理1萬個寫入消息請求。
但是如果你把消息寫入CommitLog磁盤文件的性能優化爲只需要1ms,那麼每個線程每秒可以處理1000個消息寫入,此時100個線程
每秒可以處理10萬個寫入消息請求。所以大家可以明顯看到,Broker把接收到的消息寫入CommitLog磁盤文件的性能,對他的TPS有
很大的影響。
所以在這裏,Broker是基於OS操作系統的PageCache和順序寫兩個機制,來提升寫入CommitLog文件的性能的。
首先Broker是以順序的方式將消息寫入CommitLog磁盤文件的,也就是每次寫入就是在文件末尾追加一條數據就可以了,對文件進行
順序寫的性能要比對文件隨機寫的性能提升很多
我們看下面圖裏的紅圈,就是示意數據是順序寫入的












另外,數據寫入CommitLog文件的時候,其實不是直接寫入底層的物理磁盤文件的,而是先進入OS的PageCache內存緩存中,然後後
續由OS的後臺線程選一個時間,異步化的將OS PageCache內存緩衝中的數據刷入底層的磁盤文件。
我們看下面的圖,圖裏示意出了,數據先寫入OS的PageCache緩存中,然後後續由OS自己的線程將緩存裏的數據刷入磁盤中。



所以在這樣的優化之下,採用磁盤文件順序寫+OS PageCache寫入+OS異步刷盤的策略,基本上可以讓消息寫入CommitLog的性能
跟你直接寫入內存裏是差不多的,所以正是如此,纔可以讓Broker高吞吐的處理每秒大量的消息寫入


5、同步刷盤與異步刷盤


想必很多朋友此時可能意識到一個問題了,那麼如果採用上述的模式,不就是異步刷盤的模式嗎?
對的,在上述的異步刷盤模式下,生產者把消息發送給Broker,Broker將消息寫入OS PageCache中,就直接返回ACK給生產者了。
此時生產者就認爲消息寫入成功了,那麼會有什麼問題嗎?
問題肯定是有的,如果生產者認爲消息寫入成功了,但是實際上那條消息此時是在Broker機器上的os cache中的,如果此時Broker直
接宕機,那麼是不是os cache中的這條數據就會丟失了?
我們看下面的圖,紅圈圈出來了數據早os cache裏的情況,如果此時broker宕機,那麼必然導致這裏的數據丟失,而producer還以爲
數據已經寫入成功了,以爲不會丟失,所以肯定是有問題的。
所以異步刷盤的的策略下,可以讓消息寫入吞吐量非常高,但是可能會有數據丟失的風險,這個是大家需要清除的。









另外一種模式叫做同步刷盤,如果你使用同步刷盤模式的話,那麼生產者發送一條消息出去,broker收到了消息,必須直接強制把這個
消息刷入底層的物理磁盤文件中,然後纔會返回ack給producer,此時你才知道消息寫入成功了。
只要消息進入了物理磁盤上,那麼除非是你的物理磁盤壞了導致數據丟失,否則正常來說數據就不會丟失了,我們看下面的圖,就是示
意了同步刷盤的效果




如果broker還沒有來得及把數據同步刷入磁盤,然後他自己掛了,那麼此時對producer來說會感知到消息發送失敗了,然後你只要不
停的重試發送就可以了,直到有slave broker切換成master broker重新讓你可以寫入消息,此時可以保證數據是不會丟的。
但是如果你強制每次消息寫入都要直接進入磁盤中,必然導致每條消息寫入性能急劇下降,導致消息寫入吞吐量急劇下降,但是可以保
證數據不會丟失。
好了,今天主要是分析一下broker對數據是如何存儲的,從原理角度帶着大家一步一圖來分析一下,只有具體如何切換異步刷盤和同步
刷盤的一些配置,後續我們會結合業務場景下的數據0丟失方案來講解的。





6、對今天內容的一點小小總結


今天我們講了broker最爲核心的數據存儲機制,包括如下一些知識點:
爲什麼Broker數據存儲機制是一個MQ最爲核心的環節?
CommitLog數據存儲機制
MessageQueue對應的ConsumeQueue物理位置存儲機制
基於CommitLog順序寫+OS Cache+異步刷盤的高吞吐消息寫入的機制
同步刷盤和異步刷盤各自的優缺點:高吞吐寫入+丟失數據風險,寫入吞吐量下降+數據不丟失
 






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