首先我們先看一下kafka索引的類(lèi)關(guān)系

可以看出索引主要分成三種,位移索引,時(shí)間戳索引和事務(wù)索引,都繼承于AbstractIndex
這個(gè)抽象類(lèi),我們看看AbstractIndex的定義
abstract class AbstractIndex[K, V](@volatilevar file: File, val baseOffset: Long, val maxIndexSize: Int = -1, val writable: Boolean)
file是索引文件,baseOffset是索引的起始值,
maxIndexSize是索引的最大字節(jié)數(shù),就是 Broker 端參數(shù) segment.index.bytes 的值10MB
writable是索引文件的打開(kāi)方式
AbstractIndex使用了MappedByteBuffer來(lái)存儲(chǔ)索引,使用內(nèi)存映射文件的主要優(yōu)勢(shì)在于,
它有很高的 I/O 性能,特別是對(duì)于索引這樣的小文件來(lái)說(shuō),由于文件內(nèi)存被直接映射到
虛擬內(nèi)存上,訪問(wèn)內(nèi)存映射文件的速度要快于普通的讀寫(xiě)文件速度,在linux中,
這段映射的內(nèi)存區(qū)域?qū)嶋H上就是內(nèi)核的頁(yè)緩存(Page Cache)。這就意味著里面的
數(shù)據(jù)不需要重復(fù)拷貝到用戶(hù)態(tài)空間,避免了很多不必要的時(shí)間、空間消耗。
通過(guò)往MappedByteBuffer對(duì)象里put相對(duì)位移和物理位置信息放在頁(yè)緩存,從而通過(guò)
改進(jìn)版的二分查找定位到日志文件的物理地址。
Kafka中的消息位移值是一個(gè)長(zhǎng)整型,因?yàn)閯?chuàng)建索引對(duì)象的時(shí)候,是知道起始索引baseOffset
的值的,所以只需要保存與baseOffset的差值即可,每個(gè)索引項(xiàng)可以省4個(gè)字節(jié)。

通過(guò)toRelative將傳入的long型位移轉(zhuǎn)成int的相對(duì)位移
時(shí)間戳索引也類(lèi)似,區(qū)別是保存時(shí)間戳需要8個(gè)字節(jié),通過(guò)時(shí)間戳索引可以定位到位移值。
kafka默認(rèn)是每寫(xiě)入4KB消息就生成一個(gè)索引,所以是稀疏索引,通過(guò)時(shí)間戳索引找到
滿(mǎn)足要求的消息位移值,再根據(jù)位移值定位到物理位置,然后根據(jù)entrySize8字節(jié)乘以
目標(biāo)項(xiàng)和當(dāng)前索引項(xiàng)的差值加上當(dāng)前物理位置即可定位到目標(biāo)物理位置
Page Fault
大多數(shù)操作系統(tǒng)使用頁(yè)緩存來(lái)實(shí)現(xiàn)內(nèi)存映射,而目前幾乎所有的操作系統(tǒng)都用
?LRU(Least Recently Used)或類(lèi)似于 LRU 的機(jī)制來(lái)管理頁(yè)緩存,所以有的緩存頁(yè)
會(huì)由于長(zhǎng)時(shí)間沒(méi)被訪問(wèn)而被提出緩存,舉個(gè)例子,kafka的某個(gè)索引占據(jù)了page cache的
13頁(yè),最新的數(shù)據(jù)所在的頁(yè)通常是當(dāng)前訪問(wèn)最頻繁的頁(yè),通過(guò)二分法操作順序是
0、6、9、11 、12,假如由于數(shù)據(jù)的寫(xiě)入,最新的索引項(xiàng)被保存到13了,那么二分查找的
順序就變成#0、7、10、12 和 13,由于LRU機(jī)制,之前的0、6、9、11 、12肯定被緩存著,
那么新查找的7和10這兩頁(yè)是大概率是不在緩存中的,所以發(fā)生Page Fault,需要把冷數(shù)據(jù)
從磁盤(pán)中加載到頁(yè)緩存,這個(gè)加載過(guò)程是耗時(shí)的,為了避免這種問(wèn)題,kafka不是對(duì)所有
索引項(xiàng)進(jìn)行二分查找,而是把數(shù)據(jù)分成冷熱數(shù)據(jù),從而避免無(wú)意義的Page Fault