索引文件主要是解决根据key查找消息列表的问题,那么rocketmq是怎么设计的呢?
IndexFile索引结构如下:
主要包含三部分:
A.第一部分: IndexHeader
IndexHeader索引头共占用40个字节(8byte(long类型)*4个+4byte(int类型)*2个),就是以下6个值:
beginTimestamp = new AtomicLong(0); //第一个索引消息的storeTimestamp
endTimestamp = new AtomicLong(0); //最后一个索引消息的storeTimestamp
beginPhyOffset = new AtomicLong(0); //第一个索引消息的commitLogOffset
endPhyOffset = new AtomicLong(0); //最后一个索引消息的commitLogOffset
hashSlotCount = new AtomicInteger(0); // 目前索引占用的槽数量
indexCount = new AtomicInteger(1); //当前已写入的索引数量,用于判断总数必须 < 500,0000
B、第二部分:HashSlot
一个索引文件可以保存500w个索引,HashSlot设置了500w个卡槽,每个卡槽代表一类hash值的最后一个索引的计数,所以HashSlot长度共:500w*4byte;
rocket索引是使用链表的形式来存储,类似于hashmap的结构,hash值相同的key用链结构连接起来。
C、第三部分:索引内容
每一个索引结构如下:
keyhash: key的hash值
phyOffset:消息的commitLogOffset
timeOffset:消息的storeTimeStamp和IndexHeader的startTimestamp的时间差
slotValue:链表迭代的上一个索引位置
每个索引需要20byte,总共即是:20byte*500w
这样讲还是比较抽象,我们来举一个例子:
IndexHeader就不说了,理解起来比较简单。
举例说明:
①新发送一个消息,消息key=abc,假设计算的hash值为:96354
那么先找到卡槽96354位置的值,由于是第一个索引,所以值为0
由于是第一个索引,所以将数据放入到索引1的位置,
并修改卡槽96354的值为 1 ,表示是第一个索引
如图:
②新发送一个消息,消息key=abd,假设计算的hash值为:96355
那么先找到卡槽96355位置的值,发现值为0(从没有存储过hash=96355的索引)
由于之前已经有了一个索引,所以将数据放入到索引2的位置,
并修改卡槽96355的值为 2 ,表示目前hash=96355的key对应的索引位置是第2个
如图:
③新发送一个消息,消息key=xxx,假设计算的hash值为:96354+500w
那么对应的卡槽位置为:(96354+500w)%500w=96354
那么先找到卡槽96354位置的值,发现值为1(表示当前已经有一个索引数据,并且在第一个索引位置)
当前是第三个索引,将数据放入到第三个索引位置,并且索引3数据中的卡槽值设置为上一个索引的位置,即:卡槽96354的值1。
并修改卡槽96354的值为3 ,表示目前hash=96354的key对应的索引位置是第3个
如图:
④同理,假如再发送一个消息,hash值同样为 96354
查询举例:
假设现在根据key=abc查询,计算出key的hash值=96354
那么查询的步骤如下:
1、根据key hash得知hash = 96354,计算卡槽位置,得知 96354 % 500,0000 = 96354
2、读取卡槽96354处的值,得到结果为4
3、读取索引4位置的消息所在phyOffset,则可以获取到消息内容,同时可获取到索引4处(上一卡槽值) 为 3
4、然后读取第三个索引的值,获取到索引3处(上一卡槽值)为1
5、以此类推,读取到索引1处 的key
最终获取到索引1、索引3、索引4
再根据commitLogOffset获取到消息内容,同时获取到消息key,比较发现key不同,则忽略