rocketmq消息索引是怎麼做的
參考文章
-
形象的圖示舉例很棒:https://kunzhao.org/docs/rocketmq/rocketmq-message-indexing-flow/
-
寫簡單代碼解析rocketmq的消息索引文件:https://juejin.im/post/5eaa6add6fb9a04359028457
在使用rmq的過程中覺得他的消息檢索很方便,像一個db一樣,很好奇它在數據量這麼大的情況下,是怎麼做到根據messageId、message-key、時間範圍這些因素去快速的搜索的。
首先根據messageId原理比較簡單,messageId裏包含了broker地址信息和在commitLog裏的offset,通過這兩個信息我們就可以去對應的broker裏去查找CommitLog的具體offset就可以拿到消息了。解析方法在org.apache.rocketmq.common.message.MessageDecoder#decodeMessageId。
by the way,這種在id裏存儲信息的做法在elasticsearch裏也有做,默認生成的doc_id是有包含node信息的,這種特性在排查問題的時候還是很有用的。
下面主要介紹message-key和時間範圍搜索是怎麼做到的。
消息索引服務
有專門的索引文件,存儲在 store/index下,一個索引文件相當於一個很大的hash表,整個文件結構有3部分,這3部分從頭至尾依次排開
- index_header:存儲了當前索引文件的一些全局信息,比如存儲的消息的最大最小offset、store的時間的最大最小值,已使用的hashslot數量,已使用的index item數量
- hash slot list:hash槽列表,由五百萬個hash槽組成,每個槽存儲“產生hash碰撞”的鏈表的頭結點
- index item list:真正的索引信息,由一個個的index-item(兩千萬個位置)組成,每個index-item存儲key的hash值、物理偏移量、時間戳以及一個指針(與其發生hash碰撞的下一個index-item的地址)
作爲索引文件來說,功能主要有兩個,一個是put、一個是get
put——加入新消息
前幾步都是經典的hash取模操作
-
根據消息的key計算hash值
-
hash對500w取模,找到對應的hash-slot
-
拿到找到的hash-slot的已有的值a(指的是最近在這個slot發生hash碰撞的index-item的地址,默認是0),順序從index-item-list找一個位置b,將消息的hash值、物理偏移量、時間戳和剛剛拿到的最新發生hash碰撞的index-item的地址a組成一個新的index-item放到位置b。所以這裏可以看到rmq在消息索引這裏解決hash衝突的方式也是一種拉鍊法,而且個人覺得這個拉鍊法也是使用頭插法處理新來的entry的
get——根據key查詢消息
先根據查詢的時間戳範圍,過濾不在此範圍內的文件,然後在過濾後的每個文件裏執行查找操作
- 計算查詢的key的hash值
- hash值取模,找到對應的hash-slot
- 從hash-slot拿到的第一個index-item開始遍歷這個鏈表,遇到匹配的消息就收集他們的偏移量,最後統一去commitLog文件取出消息。注意,因爲這裏我們的index-item沒有存儲具體的key值,只存了key值的hash值,所以沒有辦法像Java的hashMap那樣在發生hash衝突的鏈表上使用key值的具體內容一個一個的equals尋找真正需要的值,只能按照時間範圍和已有的hash值對比拿到一系列的msg的offset,然後去commitLog裏去查,查完之後再進行過濾。