1、為什么Broker數(shù)據(jù)存儲(chǔ)是最重要的一個(gè)環(huán)節(jié)?
上次給大家分享完P(guān)roducer的工作原理之后,團(tuán)隊(duì)整體都對(duì)RocketMQ的數(shù)據(jù)分片機(jī)制以及發(fā)送消息的時(shí)候如何寫入各個(gè)Broker機(jī)器有了一定的了解。接著就開始來給大家分享最為重要的Broker數(shù)據(jù)存儲(chǔ)的環(huán)節(jié)。
首先我們得明確一點(diǎn),為什么Broker數(shù)據(jù)存儲(chǔ)是最重要的一個(gè)環(huán)節(jié)?
很簡(jiǎn)單,實(shí)際上類似RocketMQ、Kafka、RabbitMQ的消息中間件系統(tǒng),他們不只是讓你寫入消息和獲取消息那么簡(jiǎn)單,他們本身最重要的就是提供強(qiáng)大的數(shù)據(jù)存儲(chǔ)能力,可以把億萬級(jí)的海量消息存儲(chǔ)在自己的服務(wù)器的磁盤上。
這樣的話,各種不同的系統(tǒng)從MQ中消費(fèi)消息的時(shí)候,才可以從MQ服務(wù)器的磁盤中讀取到自己需要的消息。
否則如果MQ不在機(jī)器磁盤上存儲(chǔ)大量的消息,如果消息都放在自己的內(nèi)存里,一個(gè)是內(nèi)存很可能放不下,另外一個(gè)是可能你機(jī)器重啟,內(nèi)存里的消息就會(huì)全部丟失了。
所以大家首先要明確一點(diǎn),Broker數(shù)據(jù)存儲(chǔ)實(shí)際上才是一個(gè)MQ最核心的環(huán)節(jié),他決定了生產(chǎn)者消息寫入的吞吐量,決定了消息不能丟失,決定了消費(fèi)者獲取消息的吞吐量,這些都是由他決定的。
所以今天我們來深入的探索一下Broker的數(shù)據(jù)存儲(chǔ)機(jī)制。
2、CommitLog消息順序?qū)懭霗C(jī)制
首先我們來思考一下,當(dāng)生產(chǎn)者的消息發(fā)送到一個(gè)Broker上的時(shí)候,他接收到了一條消息,接著他會(huì)對(duì)這個(gè)消息做什么事情?
首先第一步,他會(huì)把這個(gè)消息直接寫入磁盤上的一個(gè)日志文件,叫做CommitLog,直接順序?qū)懭脒@個(gè)文件,如下圖。

這個(gè)CommitLog是很多磁盤文件,每個(gè)文件限定最多1GB,Broker收到消息之后就直接追加寫入這個(gè)文件的末尾,就跟上面的圖里一樣。如果一個(gè)CommitLog寫滿了1GB,就會(huì)創(chuàng)建一個(gè)新的CommitLog文件。
3、MessageQueue在數(shù)據(jù)存儲(chǔ)中是體現(xiàn)在哪里呢?
接著我們會(huì)發(fā)現(xiàn)一個(gè)問題,如果寫入這個(gè)Broker的消息都是進(jìn)入到CommitLog中去存儲(chǔ)的,那么上次我們提到的MessageQueue是體現(xiàn)在哪里的呢?
其實(shí)在Broker中,對(duì)Topic下的每個(gè)MessageQueue都會(huì)有一系列的ConsumeQueue文件。
這是什么意思呢?
就是在Broker的磁盤上,會(huì)有下面這種格式的一系列文件:
$HOME/store/consumequeue/{topic}/{queueId}/{fileName}
上面那一串東西是什么意思?
我們之前說過,對(duì)每個(gè)Topic你不是在這臺(tái)Broker上都會(huì)有一些MessageQueue嗎?所以你會(huì)看到,{topic}指代的就是某個(gè)Topic,{queueId}指代的就是某個(gè)MessageQueue。
然后對(duì)存儲(chǔ)在這臺(tái)Broker機(jī)器上的Topic下的一個(gè)MessageQueue,他有很多的ConsumeQueue文件,這個(gè)ConsumeQueue文件里存儲(chǔ)的是一條消息對(duì)應(yīng)在CommitLog文件中的offset偏移量。
很多人可能看到這里就直接看暈了,沒明白這個(gè)是什么意思。。。
沒關(guān)系,我們一步一圖來給大家說明一下這是怎么回事。
首先我們假設(shè)有一個(gè)Topic,他有4個(gè)MessageQueue,然后在兩臺(tái)Broker機(jī)器上,每臺(tái)Broker機(jī)器會(huì)存儲(chǔ)兩個(gè)MessageQueue。
那么此時(shí)假設(shè)生產(chǎn)者選擇對(duì)其中一個(gè)MessageQueue寫入了一條消息,此時(shí)消息會(huì)發(fā)送到Broker上。
然后Broker必然會(huì)把這個(gè)消息寫入自己的CommitLog文件中,是不是?
好,我們看下面的圖里,我用紅圈畫出來了一個(gè)消息,我們假設(shè)就是剛剛寫入的消息。

我們繼續(xù)看下面的圖,我在圖里加入了兩個(gè)ConsumeQueue,分別叫做ConsumeQueue0和ConsumeQueue1,他們分別對(duì)應(yīng)著Topic里的MessageQueue0和MessageQueue1。

也就是說,Topic下的MessageQueue0和MessageQueue1就放在這個(gè)Broker機(jī)器上,而且他們每個(gè)MessageQueue目前在磁盤上就對(duì)應(yīng)了一個(gè)ConsumeQueue,所以就是MessageQueue0對(duì)應(yīng)著Broker磁盤上的ConsumeQueue0,MessageQueue1對(duì)應(yīng)著磁盤上的ConsumeQueue1。
接著假設(shè)Queue的名字叫做:TopicOrderPaySuccess,那么此時(shí)在Broker磁盤上應(yīng)該有如下兩個(gè)路徑的文件:
$HOME/store/consumequeue/TopicOrderPaySuccess/MessageQueue0/ConsumeQueue0磁盤文件
$HOME/store/consumequeue/TopicOrderPaySuccess/MessageQueue1/ConsumeQueue1磁盤文件
然后呢,當(dāng)你的Broker收到一條消息寫入了CommitLog之后,其實(shí)他同時(shí)會(huì)將這條消息在CommitLog中的物理位置,也就是一個(gè)文件偏移量,就是一個(gè)offset,寫入到這條消息所屬的MessageQueue對(duì)應(yīng)的ConsumeQueue文件中去。
比如現(xiàn)在這條消息在生產(chǎn)者發(fā)送的時(shí)候是發(fā)送給MessageQueue0的,那么此時(shí)Broker就會(huì)將這條消息在CommitLog中的offset偏移量,寫入到MessageQueue0對(duì)應(yīng)的ConsumeQueue0中去,如下圖所示。

所以實(shí)際上,ConsumeQueue0中存儲(chǔ)的是一個(gè)一個(gè)消息在CommitLog文件中的物理位置,也就是offset
所以其實(shí)大家看下面的圖,圖里展示出來的是ConsumeQueue中的一個(gè)物理位置其實(shí)是對(duì)CommitLog文件中一個(gè)消息的引用

實(shí)際上在ConsumeQueue中存儲(chǔ)的每條數(shù)據(jù)不只是消息在CommitLog中的offset偏移量,還包含了消息的長(zhǎng)度,以及tag hashcode,一條數(shù)據(jù)是20個(gè)字節(jié),每個(gè)ConsumeQueue文件保存30萬條數(shù)據(jù),大概每個(gè)文件是5.72MB。
所以實(shí)際上Topic的每個(gè)MessageQueue都對(duì)應(yīng)了Broker機(jī)器上的多個(gè)ConsumeQueue文件,保存了這個(gè)MessageQueue的所有消息在CommitLog文件中的物理位置,也就是offset偏移量。
4、如何讓消息寫入CommitLog文件近乎內(nèi)存寫性能的?
接著我們給大家講一個(gè)比較關(guān)鍵的概念:對(duì)于生產(chǎn)者把消息寫入到Broker時(shí),Broker會(huì)直接把消息寫入磁盤上的CommitLog文件,那么Broker是如何提升整個(gè)過程的性能的呢?
因?yàn)檫@個(gè)部分的性能提升會(huì)直接提升Broker處理消息寫入的吞吐量,比如你寫入一條消息到CommitLog磁盤文件假設(shè)需要10ms,那么每個(gè)線程每秒可以處理100個(gè)寫入消息,假設(shè)有100個(gè)線程,每秒只能處理1萬個(gè)寫入消息請(qǐng)求。
但是如果你把消息寫入CommitLog磁盤文件的性能優(yōu)化為只需要1ms,那么每個(gè)線程每秒可以處理1000個(gè)消息寫入,此時(shí)100個(gè)線程每秒可以處理10萬個(gè)寫入消息請(qǐng)求。所以大家可以明顯看到,Broker把接收到的消息寫入CommitLog磁盤文件的性能,對(duì)他的TPS有很大的影響。
所以在這里,Broker是基于OS操作系統(tǒng)的PageCache和順序?qū)憙蓚€(gè)機(jī)制,來提升寫入CommitLog文件的性能的。
首先Broker是以順序的方式將消息寫入CommitLog磁盤文件的,也就是每次寫入就是在文件末尾追加一條數(shù)據(jù)就可以了,對(duì)文件進(jìn)行順序?qū)懙男阅芤葘?duì)文件隨機(jī)寫的性能提升很多。
我們看下面圖里的紅圈,就是示意數(shù)據(jù)是順序?qū)懭氲摹?/p>

另外,數(shù)據(jù)寫入CommitLog文件的時(shí)候,其實(shí)不是直接寫入底層的物理磁盤文件的,而是先進(jìn)入OS的PageCache內(nèi)存緩存中,然后后續(xù)由OS的后臺(tái)線程選一個(gè)時(shí)間,異步化的將OS PageCache內(nèi)存緩沖中的數(shù)據(jù)刷入底層的磁盤文件。
我們看下面的圖,圖里示意出了,數(shù)據(jù)先寫入OS的PageCache緩存中,然后后續(xù)由OS自己的線程將緩存里的數(shù)據(jù)刷入磁盤中。

所以在這樣的優(yōu)化之下,采用磁盤文件順序?qū)?OS PageCache寫入+OS異步刷盤的策略,基本上可以讓消息寫入CommitLog的性能跟你直接寫入內(nèi)存里是差不多的,所以正是如此,才可以讓Broker高吞吐的處理每秒大量的消息寫入。
5、同步刷盤與異步刷盤
想必很多朋友此時(shí)可能意識(shí)到一個(gè)問題了,那么如果采用上述的模式,不就是異步刷盤的模式嗎?
對(duì)的,在上述的異步刷盤模式下,生產(chǎn)者把消息發(fā)送給Broker,Broker將消息寫入OS PageCache中,就直接返回ACK給生產(chǎn)者了。
此時(shí)生產(chǎn)者就認(rèn)為消息寫入成功了,那么會(huì)有什么問題嗎?
問題肯定是有的,如果生產(chǎn)者認(rèn)為消息寫入成功了,但是實(shí)際上那條消息此時(shí)是在Broker機(jī)器上的os cache中的,如果此時(shí)Broker直接宕機(jī),那么是不是os cache中的這條數(shù)據(jù)就會(huì)丟失了?
我們看下面的圖,紅圈圈出來了數(shù)據(jù)早os cache里的情況,如果此時(shí)broker宕機(jī),那么必然導(dǎo)致這里的數(shù)據(jù)丟失,而producer還以為數(shù)據(jù)已經(jīng)寫入成功了,以為不會(huì)丟失,所以肯定是有問題的。
所以異步刷盤的的策略下,可以讓消息寫入吞吐量非常高,但是可能會(huì)有數(shù)據(jù)丟失的風(fēng)險(xiǎn),這個(gè)是大家需要清除的。

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

如果broker還沒有來得及把數(shù)據(jù)同步刷入磁盤,然后他自己掛了,那么此時(shí)對(duì)producer來說會(huì)感知到消息發(fā)送失敗了,然后你只要不停的重試發(fā)送就可以了,直到有slave broker切換成master broker重新讓你可以寫入消息,此時(shí)可以保證數(shù)據(jù)是不會(huì)丟的。
但是如果你強(qiáng)制每次消息寫入都要直接進(jìn)入磁盤中,必然導(dǎo)致每條消息寫入性能急劇下降,導(dǎo)致消息寫入吞吐量急劇下降,但是可以保證數(shù)據(jù)不會(huì)丟失。
好了,今天主要是分析一下broker對(duì)數(shù)據(jù)是如何存儲(chǔ)的,從原理角度帶著大家一步一圖來分析一下,只有具體如何切換異步刷盤和同步刷盤的一些配置,后續(xù)我們會(huì)結(jié)合業(yè)務(wù)場(chǎng)景下的數(shù)據(jù)0丟失方案來講解的。
6、對(duì)今天內(nèi)容的一點(diǎn)小小總結(jié)
今天我們講了broker最為核心的數(shù)據(jù)存儲(chǔ)機(jī)制,包括如下一些知識(shí)點(diǎn):
為什么Broker數(shù)據(jù)存儲(chǔ)機(jī)制是一個(gè)MQ最為核心的環(huán)節(jié)?
CommitLog數(shù)據(jù)存儲(chǔ)機(jī)制
MessageQueue對(duì)應(yīng)的ConsumeQueue物理位置存儲(chǔ)機(jī)制
基于CommitLog順序?qū)?OS Cache+異步刷盤的高吞吐消息寫入的機(jī)制
同步刷盤和異步刷盤各自的優(yōu)缺點(diǎn):高吞吐寫入+丟失數(shù)據(jù)風(fēng)險(xiǎn),寫入吞吐量下降+數(shù)據(jù)不丟失