深入理解 RocketMQ 是如何做到高性能的?
1、RocketMQ 的核心 Broker
對 rocketmq 稍有了解的同學,都知道它主要由 4 部分組成,Producer、Consumer、Broker、NameServer。
Broker 作爲 Rocket MQ 的核心,提供了強大的數據存儲能力,可以把億萬級的消息存儲在服務器磁盤上。它決定了生產者寫入的吞吐量,決定了消息不能丟失,決定了消費者消費消息的吞吐量。
2、消息寫入磁盤文件:CommitLog
當生產者的消息發送到一個 Broker 上的時候,它接收到了一條消息,會對這個消息做什麼處理?
首先第一步,他會把這個消息直接寫入磁盤上的一個日誌文件,沒錯就是磁盤文件,它叫做 CommitLog,直接順序寫入這個文件,如下圖。
圖 1 消息順序寫入 CommitLog 文件
這個 CommitLog 文件默認大小是 1G,如果消息很多的話,可能會創建很多個 CommitLog 文件。
CommitLog 文件的文件名長度爲 20 位,左邊補零,剩餘爲起始偏移量,比如 00000000000000000000 代表了第一個文件,起始偏移量爲 0,文件大小爲 1G=1073741824;當第一個文件寫滿了,第二個文件爲 00000000001073741824,起始偏移量爲 1073741824,以此類推。
圖 2 CommitLog 文件
Broker 收到消息後,直接追加到這個文件的末尾。
3、消息的偏移量寫入磁盤文件:ConsumeQueue
我們知道每個 Topic 可能對應了多個 Queue,那麼這些 Queue 在 Broker 中是如何體現的呢?
其實在 Broker 中,每個 Topic 下的每個 Queue 都會對應一些列的 ConsumeQueue 文件。
圖 3 Broker 本地存儲文件
就是在 Broker 磁盤上,會有下面這種格式的一些列文件:
~/store/consumequeue/{topic}/{queueId}/{fileName}
{topic} 指代的就是某個 Topic,{queueId} 指代的就是某個 MessageQueue。
然後存儲在這臺 Broker 機器上的 Topic 下的一個 MessageQueue,他有很多的 ConsumeQueue 文件,這個 ConsumeQueue 文件裏存儲的是一條消息對應在 CommitLog 文件中的 offset 偏移量。
這點比較重要,ConsumeQueue 不存儲真實的消息數據,只存消息數據在 CommitLog 文件中的偏移量。
假設有一個 Topic,他有 4 個 Queue,然後分佈在兩臺 Broker 機器上,每臺 Broker 機器會存儲兩個 Queue。
此時生產者選擇對其中一個 Queue 寫入了一條消息,此時消息會發送到 Broker 上。
然後然後 Broker 會把這個消息寫入 CommitLog 文件中,同時會把消息的偏移量寫入兩個 ConsumeQueue 中,ConsumeQueue0 和 ConsumeQueue1。它們分別對應着 Topic 裏的 Queue0 和 Queue1。
圖 4 消息偏移量寫入 ConsumeQueue 文件中
也就是說,Topic 下的 Queue0 和 Queue1 就放在這個 Broker 機器上,而它們每個在磁盤上對應了一個 ConsumeQueue 文件,所以就是 Queue0 對應着 Broker 磁盤上的 ConsumeQueue0,Queue1 對應着磁盤上的 ConsumeQueue1。
假設 Queue 的名字叫做:TopicOrderInfo,Queue0 的 id 是 0,Queue1 的 id 是 1,那麼此時在 Broker 磁盤上應該有如下兩個路徑的文件:
~/store/consumequeue/TopicOrderInfo/0/ConsumeQueue0 文件
~/store/consumequeue/TopicOrderInfo/1/ConsumeQueue1 文件
圖 5 ConsumeQueue 本地磁盤文件
ConsumeQueue 文件存在的目的就是可以快速定位到消息真實的物理位置,在 ConsumeQueue 中存儲的每條數據不只是消息在 CommitLog 中的 offset 偏移量,還包含了消息的長度,以及 tag hashcode,一條數據是 20 個字節,每個 ConsumeQueue 文件保存 30 萬條數據,所以計算下來每個文件是 5.72MB。
需要注意的是每個 Topic 的每個 Queue 都對應了 Broker 機器上的多個 ConsumeQueue 文件,保存了這個 MessageQueue 的所有消息在 CommitLog 文件中的物理位置,也就是 offset 偏移量。
4、RocketMQ 是如何提升 CommitLog 寫入性能的?
CommitLog 作爲存儲消息的核心所在,關乎着整個消息隊列的吞吐量。那麼 Broker 是如何提升整個過程的性能的呢?
Broker 是基於 OS 操作系統的 PageCache 和順序寫兩個機制,來提升寫入 CommitLog 文件的性能的。
首先 Broker 是以順序的方式將消息寫入 CommitLog 磁盤文件的,也就是每次寫入就是在文件末尾追加一條數據就可以了,對文件進行順序寫的性能要比對文件隨機寫的性能提升很多。
另外,消息寫入 CommitLog 文件的時候,並不是直接寫入磁盤文件的,而是先進入 OS 的 PageCache 內存緩存中,然後再由 OS 的後臺線程選一個時間,異步化的將 OS PageCache 內存緩衝中的數據刷入底層的磁盤文件。
圖 6 異步刷盤 CommitLog 文件
在採用磁盤文件順序寫 + OS PageCache 寫入 + OS 異步刷盤的策略,基本上可以讓消息寫入 CommitLog 的性能接近直接寫入內存,所以正是如此,纔可以讓 Broker 高吞吐的處理每秒大量的消息寫入。
5、異步刷盤的利弊
很多時候魚和熊掌不可兼得,我們充分提升性能的同時,就會犧牲一些高可用性。
如果生產者認爲消息寫入成功了,但是實際上那條消息此時是在 Broker 機器上的 os cache 中的,如果此時 Broker 直接宕機,那麼是不是 os cache 中的這條數據就會丟失了?
所以異步刷盤的的策略下,可以讓消息寫入吞吐量非常高,但是可能會有數據丟失的風險。
所以 rocketmq 提供了同步刷盤,讓使用者可選擇。如果你使用同步刷盤模式的話,那麼生產者發送一條消息出去,broker 收到了消息,必須直接強制把這個消息刷入底層的物理磁盤文件中,然後纔會返回 ack 給 producer,此時你才知道消息寫入成功了。
但是如果你強制每次消息寫入都要直接進入磁盤中,必然導致每條消息寫入性能急劇下降,導致消息寫入吞吐量急劇下降,但是可以保證數據不會丟失。具體如何選擇,還需要看你的業務場景。
6、總結
這篇文章主要講 broker 最爲核心的數據存儲機制,希望夥伴們不只是記住,也要理解思考,爲什麼這樣設計,自己的項目中是否有可以借鑑學習的地方。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lzvwtje5vNDdAP3kYAO8jw