三: indexFile
除了通過通常的指定Topic進行消息消費外,RocketMQ還提供了根據key進行消息查詢的功能。
該查詢是通過store目錄中的index子目錄中的indexFile進行索引實現的快速查詢。
這個indexFile中的索引數據是在如果包含了key的消息被生產者發送到Broker時寫入的。
如果消息中沒有包含key,則不會寫入。
1. 索引條目結構
每個Broker中會包含一組indexFile,每個indexFile都是以一個時間戳命名的(這個indexFile被創建時 的時間戳)。
每個indexFile文件由三部分構成:indexHeader,slots槽位,indexes索引數據。
每個 indexFile文件中包含500w個slot槽。而每個slot槽又可能會掛載很多的index索引單元。
indexFile的文件結構如下:
其中slots中的每個slot槽掛載的index索引單元都來源於indexes區中

indexHeader:
固定40個字節,其中存放着如下數據:

- beginTimestamp:該indexFile中第一條消息的存儲時間
- endTimestamp:該indexFile中最后一條消息存儲時間
- beginPhyoffset:該indexFile中第一條消息在commitlog中的偏移量commitlog offset
- endPhyoffset:該indexFile中最后一條消息在commitlog中的偏移量commitlog offset
- hashSlotCount:已經填充有index的slot數量(並不是每個slot槽下都掛載有index索引單元,這 里統計的是所有掛載了index索引單元的slot槽的數量)
- indexCount:該indexFile中包含的索引單元個數(統計出當前indexFile中所有slot槽下掛載的所有index索引單元的數量之和)
indexFile中最復雜的是Slots與Indexes間的關系。在實際存儲時,Indexes是在Slots后面的,但為了便於理解,將它們的關系展示為如下形式:

每條消息key的hash值 % 500w的結果即為slot槽位,然后該slot記錄此index索引單元的indexNo,根 據這個indexNo可以計算出該index單元在indexFile中的位置。
不過,該取模結果的重復率是很高的, 為了解決該問題,在每個index索引單元中增加了preIndexNo,用於指定該slot中當前index索引單元的前一個index索引單元。(如圖所示,每個index索引單元都記錄此slot的前一個索引單元)
而slot中始終存放的是其下最新的index索引單元的indexNo,這樣的話,只要找到了slot就可以找到其最新的index索引單元,而通過這個index索引單元就可以依次找到其之前的所有 index索引單元。
slot記錄的索引單元的indexNo是index索引單元在indexFile中的流水號,從0開始依次遞增。即在一個indexFile中所有indexNo是 以此遞增的。
indexNo在index索引單元中是沒有維護的,其是通過indexes中依次數出來的。
index索引單元默寫20個字節,主要存放某條消息key值和地址,其中存放着以下四個屬性:

- keyHash:消息中指定的業務key的hash值
- phyOffset:當前key對應的消息在commitlog中的偏移量commitlog offset
- timeDiff:當前key對應消息的存儲時間與當前indexFile創建時間的時間差
- preIndexNo:當前slot下當前index索引單元的前一個index索引單元的indexNo
2 indexFile的創建
indexFile的文件名為當前文件被創建時的時間戳。這個時間戳有什么用處呢?
根據業務key進行查詢時,查詢條件除了key之外,還需要指定一個要查詢的時間戳,表示要查詢不大於 該時間戳的最新的消息,即查詢指定時間戳之前存儲的最新消息。這個時間戳文件名可以簡化查詢,提高查詢效率。具體后面會詳細講解。
indexFile文件是何時創建的?其創建的條件(時機)有兩個:
-
當第一條帶key的消息發送來后,系統發現沒有indexFile,此時會創建第一個indexFile文件
-
當一個indexFile中掛載的index索引單元數量超出2000w個時,會創建新的indexFile。
當帶key的 消息發送到來后,系統會找到最新的indexFile,並從其indexHeader的最后4字節中讀取到 indexCount。若indexCount >= 2000w時,會創建新的indexFile。
由於可以推算出,一個indexFile的最大大小是:(40 + 500w * 4 + 2000w * 20)字節
3. 查詢流程
當消費者通過業務key來查詢相應的消息時,其需要經過一個相對較復雜的查詢流程。不過,在分析查詢流程之前,首先要清楚幾個定位計算公式:
式子1: 計算指定消息key的slot槽位序號:
slot槽位序號 = key的hash % 500w
式子2: 計算槽位序號為n的slot在indexFile中的起始位置:
slot(n)位置 = 40 + (n - 1) * 4
式子3: 計算indexNo為m的index在indexFile中的位置:
index(m)位置 = 40 + 500w * 4 + (m - 1) * 20
說明:
- 40為indexFile中indexHeader的字節數
- 500w * 4 是所有slots所占的字節數
具體查詢流程如下:

這里解釋一下為什么需要計算查詢時間和文件創建時間的差值, 並和 index 索引單元的timeDiff值做比較,
這兩個值都是記錄的和indexFile文件創建時間的差值,
所以如果 index 索引單元的timeDiff大於查詢時間的timeDiff,說明此index索引單元在需要查詢的時間之后創建的,即不滿足需要查詢的時間條件
