RocketMQ消息存儲


轉載:RocketMQ源碼學習--消息存儲篇

消息中間件—RocketMQ消息存儲(一)

RocketMQ高性能之底層存儲設計

存儲架構

RMQ存儲架構

上圖即為RocketMQ的消息存儲整體架構,RocketMQ采用的是混合型的存儲結構,即為Broker單個實例下所有的隊列共用一個日志數據文件(即為CommitLog,1G)來存儲。

Consume Queue相當於kafka中的partition,是一個邏輯隊列,存儲了這個Queue在CommiLog中的     起始offset,log大小和MessageTag的hashCode。

每次讀取消息隊列先讀取consumerQueue,然后再通過consumerQueue去commitLog中拿到消息主體。

Kafka存儲架構

rocketMQ的設計理念很大程度借鑒了kafka,所以有必要介紹下kafka的存儲結構設計:

存儲特點: 和RocketMQ類似,每個Topic有多個partition(queue),kafka的每個partition都是一個獨立的物理文件,消息直接從里面讀寫。

根據之前阿里中間件團隊的測試,一旦kafka中Topic的partitoin數量過多,隊列文件會過多,會給磁盤的IO讀寫造成很大的壓力,造成tps迅速下降。

所以RocketMQ進行了上述這樣設計,consumerQueue中只存儲很少的數據,消息主體都是通過CommitLog來進行讀寫。

ps:上一行加粗理解:consumerQueue存儲少量數據,即使數量很多,但是數據量不大,文件可以控制得非常小,絕大部分的訪問還是Page Cache的訪問,而不是磁盤訪問。正式部署也可以將CommitLog和consumerQueue放在不同的物理SSD,避免多類文件進行IO競爭。

RMQ存儲設計優缺點

優點:

隊列輕量化,單個隊列數據量非常少。對磁盤的訪問串行化,避免磁盤竟爭,不會因為隊列增加導致IOWAIT增高。

缺點:

寫雖然完全是順序寫,但是讀卻變成了完全的隨機讀。

讀一條消息,會先讀ConsumeQueue,再讀CommitLog,增加了開銷。

要保證CommitLog與ConsumeQueue完全的一致,增加了編程的復雜度。

缺點克服:

隨機讀,盡可能讓讀命中page cache,減少IO讀操作,所以內存越大越好。如果系統中堆積的消息過多,讀數據要訪問磁盤會不會由於隨機讀導致系統性能急劇下降,答案是否定的。 
訪問page cache 時,即使只訪問1k的消息,系統也會提前預讀出更多數據,在下次讀時,就可能命中內存。 
隨機訪問Commit Log磁盤數據,系統IO調度算法設置為NOOP方式,會在一定程度上將完全的隨機讀變成順序跳躍方式,而順序跳躍方式讀較完全的隨機讀性能會高5倍以上。 
另外4k的消息在完全隨機訪問情況下,仍然可以達到8K次每秒以上的讀性能。 
由於Consume Queue存儲數據量極少,而且是順序讀,在PAGECACHE預讀作用下,Consume Queue的讀性能幾乎與內存一致,即使堆積情況下。所以可認為Consume Queue完全不會阻礙讀性能。 
Commit Log中存儲了所有的元信息,包含消息體,類似於Mysql、Oracle的redolog,所以只要有Commit Log在,Consume Queue即使數據丟失,仍然可以恢復出來。

RMQ存儲底層實現

MappedByteBuffer

RocketMQ中的文件讀寫主要就是通過MappedByteBuffer進行操作,來進行文件映射。利用了nio中的FileChannel模型,可以直接將物理文件映射到緩沖區,提高讀寫速度。

這種Mmap的方式減少了傳統IO將磁盤文件數據在操作系統內核地址空間的緩沖區和用戶應用程序地址空間的緩沖區之間來回進行拷貝的性能開銷。

這里需要注意的是,采用MappedByteBuffer這種內存映射的方式有幾個限制,其中之一是一次只能映射1.5~2G 的文件至用戶態的虛擬內存,這也是為何RocketMQ默認設置單個CommitLog日志數據文件為1G的原因了。

page cache

剛剛提到的緩沖區,也就是之前說到的page cache。

通俗的說:pageCache是系統讀寫磁盤時為了提高性能將部分文件緩存到內存中,下面是詳細解釋:

page cache:這里所提及到的page cache,在我看來是linux中vfs虛擬文件系統層的cache層,一般pageCache默認是4K大小,它被操作系統的內存管理模塊所管理,文件被映射到內存,一般都是被mmap()函數映射上去的。

mmap()函數會返回一個指針,指向邏輯地址空間中的邏輯地址,邏輯地址通過MMU映射到page cache上。

 

上圖中,整個OS有3.7G的物理內存,用掉了2.7G,應當還剩下1G空閑的內存,但OS給出的卻是175M。

因為OS發現系統的物理內存有大量剩余時,為了提高IO的性能,就會使用多余的內存當做文件緩存,也就是圖上的buff / cache,廣義我們說的Page Cache就是這些內存的子集。

pageCache缺點:

內核把可用的內存分配給Page Cache后,free的內存相對就會變少,如果程序有新的內存分配需求或者缺頁中斷,恰好free的內存不夠,內核還需要花費一點時間將熱度低的Page Cache的內存回收掉,對性能非常苛刻的系統會產生毛刺。

內存映射    零拷貝

RMQ發送、消費邏輯

發送邏輯

發送時,Producer不直接與Consume Queue打交道。上文提到過,RMQ所有的消息都會存放在Commit Log中,為了使消息存儲不發生混亂,對Commit Log進行寫之前就會上鎖。

消息持久被鎖串行化后,對Commit Log就是順序寫,也就是常說的Append操作。配合上Page Cache,RMQ在寫Commit Log時效率會非常高。

Broker端的后台服務線程—ReputMessageService不停地分發請求並異步構建ConsumeQueue(邏輯消費隊列)和IndexFile(索引文件)數據,不停的輪詢,將當前的consumeQueue中的offSet和commitLog中的offSet進行對比,將多出來的offSet進行解析,然后put到consumeQueue中的MapedFile中。

ConsumeQueue(邏輯消費隊列)作為消費消息的索引,保存了指定Topic下的隊列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。而IndexFile(索引文件)則只是為了消息查詢提供了一種通過key或時間區間來查詢消息的方法(ps:這種通過IndexFile來查找消息的方法不影響發送與消費消息的主流程)。

消費邏輯

消費時,Consumer不直接與Commit Log打交道,而是從Consume Queue中去拉取數據。拉取的順序從舊到新,在文件表示每一個Consume Queue都是順序讀,充分利用了Page Cache。光拉取Consume Queue是沒有數據的,里面只有一個對Commit Log的引用,所以再次拉取Commit Log。

但整個RMQ只有一個Commit Log,雖然是隨機讀,但整體還是有序地讀,只要那整塊區域還在Page Cache的范圍內,還是可以充分利用Page Cache。(dstat命令)

對於CommitLog消息存儲的日志數據文件來說,讀取消息內容時候會產生較多的隨機訪問讀取,嚴重影響性能。如果選擇合適的系統IO調度算法,比如設置調度算法為“Noop”(此時塊存儲采用SSD的話),隨機讀的性能也會有所提升。

刷盤方式

同步刷盤

 在消息真正落盤后,才返回成功給Producer,只要磁盤沒有損壞,消息就不會丟。一般只用於金融場景。

異步刷盤

讀寫文件充分利用了Page Cache,即寫入Page Cache就返回成功給Producer,RMQ中有兩種方式進行異步刷盤,整體原理是一樣的。

RMQ文件存儲模型層

RocketMQ文件存儲模型層次結構如上圖所示,根據類別和作用從概念模型上大致可以划分為5層,下面將從各個層次分別進行分析和闡述:

(1)RocketMQ業務處理器層

Broker端對消息進行讀取和寫入的業務邏輯入口,這一層主要包含了業務邏輯相關處理操作(根據解析RemotingCommand中的RequestCode來區分具體的業務操作類型,進而執行不同的業務處理流程),比如前置的檢查和校驗步驟、構造MessageExtBrokerInner對象、decode反序列化、構造Response返回對象等。

(2)RocketMQ數據存儲組件層

該層主要是RocketMQ的存儲核心類—DefaultMessageStore,其為RocketMQ消息數據文件的訪問入口,通過該類的“putMessage()”和“getMessage()”方法完成對CommitLog消息存儲的日志數據文件進行讀寫操作(具體的讀寫訪問操作還是依賴下一層中CommitLog對象模型提供的方法);另外,在該組件初始化時候,還會啟動很多存儲相關的后台服務線程,包括AllocateMappedFileService(MappedFile預分配服務線程)、ReputMessageService(回放存儲消息服務線程)、HAService(Broker主從同步高可用服務線程)、StoreStatsService(消息存儲統計服務線程)、IndexService(索引文件服務線程)等。

(3)RocketMQ存儲邏輯對象層

該層主要包含了RocketMQ數據文件存儲直接相關的三個模型類IndexFile、ConsumerQueue和CommitLog。IndexFile為索引數據文件提供訪問服務,ConsumerQueue為邏輯消息隊列提供訪問服務,CommitLog則為消息存儲的日志數據文件提供訪問服務。這三個模型類也是構成了RocketMQ存儲層的整體結構(對於這三個模型類的深入分析將放在后續篇幅中)。

(4)封裝的文件內存映射層

RocketMQ主要采用JDK NIO中的MappedByteBuffer和FileChannel兩種方式完成數據文件的讀寫。其中,采用MappedByteBuffer這種內存映射磁盤文件的方式完成對大文件的讀寫,在RocketMQ中將該類封裝成MappedFile類。這里限制的問題在上面已經講過;對於每類大文件(IndexFile/ConsumerQueue/CommitLog),在存儲時分隔成多個固定大小的文件(單個IndexFile文件大小約為400M、單個ConsumerQueue文件大小約5.72M、單個CommitLog文件大小為1G),其中每個分隔文件的文件名為前面所有文件的字節大小數+1,即為文件的起始偏移量,從而實現了整個大文件的串聯。這里,每一種類的單個文件均由MappedFile類提供讀寫操作服務(其中,MappedFile類提供了順序寫/隨機讀、內存數據刷盤、內存清理等和文件相關的服務)。

(5)磁盤存儲層

主要指的是部署RocketMQ服務器所用的磁盤。這里,需要考慮不同磁盤類型(如SSD或者普通的HDD)特性以及磁盤的性能參數(如IOPS、吞吐量和訪問時延等指標)對順序寫/隨機讀操作帶來的影響。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM