rocketmq存储结构_RocketMQ消息存储
存儲(chǔ)架構(gòu)
RMQ存儲(chǔ)架構(gòu)
上圖即為RocketMQ的消息存儲(chǔ)整體架構(gòu),RocketMQ采用的是混合型的存儲(chǔ)結(jié)構(gòu),即為Broker單個(gè)實(shí)例下所有的隊(duì)列共用一個(gè)日志數(shù)據(jù)文件(即為CommitLog,1G)來存儲(chǔ)。
Consume Queue相當(dāng)于kafka中的partition,是一個(gè)邏輯隊(duì)列,存儲(chǔ)了這個(gè)Queue在CommiLog中的? ? ?起始o(jì)ffset,log大小和MessageTag的hashCode。
每次讀取消息隊(duì)列先讀取consumerQueue,然后再通過consumerQueue去commitLog中拿到消息主體。
Kafka存儲(chǔ)架構(gòu)
rocketMQ的設(shè)計(jì)理念很大程度借鑒了kafka,所以有必要介紹下kafka的存儲(chǔ)結(jié)構(gòu)設(shè)計(jì):
存儲(chǔ)特點(diǎn):?和RocketMQ類似,每個(gè)Topic有多個(gè)partition(queue),kafka的每個(gè)partition都是一個(gè)獨(dú)立的物理文件,消息直接從里面讀寫。
根據(jù)之前阿里中間件團(tuán)隊(duì)的測試,一旦kafka中Topic的partitoin數(shù)量過多,隊(duì)列文件會(huì)過多,會(huì)給磁盤的IO讀寫造成很大的壓力,造成tps迅速下降。
所以RocketMQ進(jìn)行了上述這樣設(shè)計(jì),consumerQueue中只存儲(chǔ)很少的數(shù)據(jù),消息主體都是通過CommitLog來進(jìn)行讀寫。
ps:上一行加粗理解:consumerQueue存儲(chǔ)少量數(shù)據(jù),即使數(shù)量很多,但是數(shù)據(jù)量不大,文件可以控制得非常小,絕大部分的訪問還是Page Cache的訪問,而不是磁盤訪問。正式部署也可以將CommitLog和consumerQueue放在不同的物理SSD,避免多類文件進(jìn)行IO競爭。
RMQ存儲(chǔ)設(shè)計(jì)優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
隊(duì)列輕量化,單個(gè)隊(duì)列數(shù)據(jù)量非常少。對(duì)磁盤的訪問串行化,避免磁盤竟?fàn)?#xff0c;不會(huì)因?yàn)殛?duì)列增加導(dǎo)致IOWAIT增高。
缺點(diǎn):
寫雖然完全是順序?qū)?#xff0c;但是讀卻變成了完全的隨機(jī)讀。
讀一條消息,會(huì)先讀ConsumeQueue,再讀CommitLog,增加了開銷。
要保證CommitLog與ConsumeQueue完全的一致,增加了編程的復(fù)雜度。
缺點(diǎn)克服:
隨機(jī)讀,盡可能讓讀命中page cache,減少IO讀操作,所以內(nèi)存越大越好。如果系統(tǒng)中堆積的消息過多,讀數(shù)據(jù)要訪問磁盤會(huì)不會(huì)由于隨機(jī)讀導(dǎo)致系統(tǒng)性能急劇下降,答案是否定的。
訪問page cache 時(shí),即使只訪問1k的消息,系統(tǒng)也會(huì)提前預(yù)讀出更多數(shù)據(jù),在下次讀時(shí),就可能命中內(nèi)存。
隨機(jī)訪問Commit Log磁盤數(shù)據(jù),系統(tǒng)IO調(diào)度算法設(shè)置為NOOP方式,會(huì)在一定程度上將完全的隨機(jī)讀變成順序跳躍方式,而順序跳躍方式讀較完全的隨機(jī)讀性能會(huì)高5倍以上。
另外4k的消息在完全隨機(jī)訪問情況下,仍然可以達(dá)到8K次每秒以上的讀性能。
由于Consume Queue存儲(chǔ)數(shù)據(jù)量極少,而且是順序讀,在PAGECACHE預(yù)讀作用下,Consume Queue的讀性能幾乎與內(nèi)存一致,即使堆積情況下。所以可認(rèn)為Consume Queue完全不會(huì)阻礙讀性能。
Commit Log中存儲(chǔ)了所有的元信息,包含消息體,類似于Mysql、Oracle的redolog,所以只要有Commit Log在,Consume Queue即使數(shù)據(jù)丟失,仍然可以恢復(fù)出來。
RMQ存儲(chǔ)底層實(shí)現(xiàn)
MappedByteBuffer
RocketMQ中的文件讀寫主要就是通過MappedByteBuffer進(jìn)行操作,來進(jìn)行文件映射。利用了nio中的FileChannel模型,可以直接將物理文件映射到緩沖區(qū),提高讀寫速度。
這種Mmap的方式減少了傳統(tǒng)IO將磁盤文件數(shù)據(jù)在操作系統(tǒng)內(nèi)核地址空間的緩沖區(qū)和用戶應(yīng)用程序地址空間的緩沖區(qū)之間來回進(jìn)行拷貝的性能開銷。
這里需要注意的是,采用MappedByteBuffer這種內(nèi)存映射的方式有幾個(gè)限制,其中之一是一次只能映射1.5~2G 的文件至用戶態(tài)的虛擬內(nèi)存,這也是為何RocketMQ默認(rèn)設(shè)置單個(gè)CommitLog日志數(shù)據(jù)文件為1G的原因了。
page cache
剛剛提到的緩沖區(qū),也就是之前說到的page cache。
通俗的說:pageCache是系統(tǒng)讀寫磁盤時(shí)為了提高性能將部分文件緩存到內(nèi)存中,下面是詳細(xì)解釋:
page cache:這里所提及到的page cache,在我看來是linux中vfs虛擬文件系統(tǒng)層的cache層,一般pageCache默認(rèn)是4K大小,它被操作系統(tǒng)的內(nèi)存管理模塊所管理,文件被映射到內(nèi)存,一般都是被mmap()函數(shù)映射上去的。
mmap()函數(shù)會(huì)返回一個(gè)指針,指向邏輯地址空間中的邏輯地址,邏輯地址通過MMU映射到page cache上。
上圖中,整個(gè)OS有3.7G的物理內(nèi)存,用掉了2.7G,應(yīng)當(dāng)還剩下1G空閑的內(nèi)存,但OS給出的卻是175M。
因?yàn)镺S發(fā)現(xiàn)系統(tǒng)的物理內(nèi)存有大量剩余時(shí),為了提高IO的性能,就會(huì)使用多余的內(nèi)存當(dāng)做文件緩存,也就是圖上的buff / cache,廣義我們說的Page Cache就是這些內(nèi)存的子集。
pageCache缺點(diǎn):
內(nèi)核把可用的內(nèi)存分配給Page Cache后,free的內(nèi)存相對(duì)就會(huì)變少,如果程序有新的內(nèi)存分配需求或者缺頁中斷,恰好free的內(nèi)存不夠,內(nèi)核還需要花費(fèi)一點(diǎn)時(shí)間將熱度低的Page Cache的內(nèi)存回收掉,對(duì)性能非常苛刻的系統(tǒng)會(huì)產(chǎn)生毛刺。
RMQ發(fā)送、消費(fèi)邏輯
發(fā)送邏輯
發(fā)送時(shí),Producer不直接與Consume Queue打交道。上文提到過,RMQ所有的消息都會(huì)存放在Commit Log中,為了使消息存儲(chǔ)不發(fā)生混亂,對(duì)Commit Log進(jìn)行寫之前就會(huì)上鎖。
消息持久被鎖串行化后,對(duì)Commit Log就是順序?qū)?#xff0c;也就是常說的Append操作。配合上Page Cache,RMQ在寫Commit Log時(shí)效率會(huì)非常高。
Broker端的后臺(tái)服務(wù)線程—ReputMessageService不停地分發(fā)請(qǐng)求并異步構(gòu)建ConsumeQueue(邏輯消費(fèi)隊(duì)列)和IndexFile(索引文件)數(shù)據(jù),不停的輪詢,將當(dāng)前的consumeQueue中的offSet和commitLog中的offSet進(jìn)行對(duì)比,將多出來的offSet進(jìn)行解析,然后put到consumeQueue中的MapedFile中。
ConsumeQueue(邏輯消費(fèi)隊(duì)列)作為消費(fèi)消息的索引,保存了指定Topic下的隊(duì)列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。而IndexFile(索引文件)則只是為了消息查詢提供了一種通過key或時(shí)間區(qū)間來查詢消息的方法(ps:這種通過IndexFile來查找消息的方法不影響發(fā)送與消費(fèi)消息的主流程)。
消費(fèi)邏輯
消費(fèi)時(shí),Consumer不直接與Commit Log打交道,而是從Consume Queue中去拉取數(shù)據(jù)。拉取的順序從舊到新,在文件表示每一個(gè)Consume Queue都是順序讀,充分利用了Page Cache。光拉取Consume Queue是沒有數(shù)據(jù)的,里面只有一個(gè)對(duì)Commit Log的引用,所以再次拉取Commit Log。
但整個(gè)RMQ只有一個(gè)Commit Log,雖然是隨機(jī)讀,但整體還是有序地讀,只要那整塊區(qū)域還在Page Cache的范圍內(nèi),還是可以充分利用Page Cache。(dstat命令)
對(duì)于CommitLog消息存儲(chǔ)的日志數(shù)據(jù)文件來說,讀取消息內(nèi)容時(shí)候會(huì)產(chǎn)生較多的隨機(jī)訪問讀取,嚴(yán)重影響性能。如果選擇合適的系統(tǒng)IO調(diào)度算法,比如設(shè)置調(diào)度算法為“Noop”(此時(shí)塊存儲(chǔ)采用SSD的話),隨機(jī)讀的性能也會(huì)有所提升。
刷盤方式
同步刷盤
在消息真正落盤后,才返回成功給Producer,只要磁盤沒有損壞,消息就不會(huì)丟。一般只用于金融場景。
異步刷盤
讀寫文件充分利用了Page Cache,即寫入Page Cache就返回成功給Producer,RMQ中有兩種方式進(jìn)行異步刷盤,整體原理是一樣的。
RMQ文件存儲(chǔ)模型層
RocketMQ文件存儲(chǔ)模型層次結(jié)構(gòu)如上圖所示,根據(jù)類別和作用從概念模型上大致可以劃分為5層,下面將從各個(gè)層次分別進(jìn)行分析和闡述:
(1)RocketMQ業(yè)務(wù)處理器層
Broker端對(duì)消息進(jìn)行讀取和寫入的業(yè)務(wù)邏輯入口,這一層主要包含了業(yè)務(wù)邏輯相關(guān)處理操作(根據(jù)解析RemotingCommand中的RequestCode來區(qū)分具體的業(yè)務(wù)操作類型,進(jìn)而執(zhí)行不同的業(yè)務(wù)處理流程),比如前置的檢查和校驗(yàn)步驟、構(gòu)造MessageExtBrokerInner對(duì)象、decode反序列化、構(gòu)造Response返回對(duì)象等。
(2)RocketMQ數(shù)據(jù)存儲(chǔ)組件層
該層主要是RocketMQ的存儲(chǔ)核心類—DefaultMessageStore,其為RocketMQ消息數(shù)據(jù)文件的訪問入口,通過該類的“putMessage()”和“getMessage()”方法完成對(duì)CommitLog消息存儲(chǔ)的日志數(shù)據(jù)文件進(jìn)行讀寫操作(具體的讀寫訪問操作還是依賴下一層中CommitLog對(duì)象模型提供的方法);另外,在該組件初始化時(shí)候,還會(huì)啟動(dòng)很多存儲(chǔ)相關(guān)的后臺(tái)服務(wù)線程,包括AllocateMappedFileService(MappedFile預(yù)分配服務(wù)線程)、ReputMessageService(回放存儲(chǔ)消息服務(wù)線程)、HAService(Broker主從同步高可用服務(wù)線程)、StoreStatsService(消息存儲(chǔ)統(tǒng)計(jì)服務(wù)線程)、IndexService(索引文件服務(wù)線程)等。
(3)RocketMQ存儲(chǔ)邏輯對(duì)象層
該層主要包含了RocketMQ數(shù)據(jù)文件存儲(chǔ)直接相關(guān)的三個(gè)模型類IndexFile、ConsumerQueue和CommitLog。IndexFile為索引數(shù)據(jù)文件提供訪問服務(wù),ConsumerQueue為邏輯消息隊(duì)列提供訪問服務(wù),CommitLog則為消息存儲(chǔ)的日志數(shù)據(jù)文件提供訪問服務(wù)。這三個(gè)模型類也是構(gòu)成了RocketMQ存儲(chǔ)層的整體結(jié)構(gòu)(對(duì)于這三個(gè)模型類的深入分析將放在后續(xù)篇幅中)。
(4)封裝的文件內(nèi)存映射層
RocketMQ主要采用JDK NIO中的MappedByteBuffer和FileChannel兩種方式完成數(shù)據(jù)文件的讀寫。其中,采用MappedByteBuffer這種內(nèi)存映射磁盤文件的方式完成對(duì)大文件的讀寫,在RocketMQ中將該類封裝成MappedFile類。這里限制的問題在上面已經(jīng)講過;對(duì)于每類大文件(IndexFile/ConsumerQueue/CommitLog),在存儲(chǔ)時(shí)分隔成多個(gè)固定大小的文件(單個(gè)IndexFile文件大小約為400M、單個(gè)ConsumerQueue文件大小約5.72M、單個(gè)CommitLog文件大小為1G),其中每個(gè)分隔文件的文件名為前面所有文件的字節(jié)大小數(shù)+1,即為文件的起始偏移量,從而實(shí)現(xiàn)了整個(gè)大文件的串聯(lián)。這里,每一種類的單個(gè)文件均由MappedFile類提供讀寫操作服務(wù)(其中,MappedFile類提供了順序?qū)?隨機(jī)讀、內(nèi)存數(shù)據(jù)刷盤、內(nèi)存清理等和文件相關(guān)的服務(wù))。
(5)磁盤存儲(chǔ)層
主要指的是部署RocketMQ服務(wù)器所用的磁盤。這里,需要考慮不同磁盤類型(如SSD或者普通的HDD)特性以及磁盤的性能參數(shù)(如IOPS、吞吐量和訪問時(shí)延等指標(biāo))對(duì)順序?qū)?隨機(jī)讀操作帶來的影響。
總結(jié)
以上是生活随笔為你收集整理的rocketmq存储结构_RocketMQ消息存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言实现抽签小功能
- 下一篇: TortoiseSVN回退版本