文章目录
IndexFile文件讲解
之前说了RocketMQ的物理日志文件CommitLog和逻辑日志文件ConsumeQueue。现在说的是对应的消息索引文件IndexFile。
概述
IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是:HOME\store\index{fileName}
,文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构,故rocketmq的索引文件其底层实现为hash索引。
文件结构
文件的结构这里参考网上的一张图
这个图是整个IndexFile的文件结构,主要分为3部分。第一部分,文件头信息(大小为40 byte);第二部分,hash槽位(单个槽位4byte,一共500w个);第三部分,索引信息链表部分(单个index为20 byte,一共2000w个)。分三部分进行说明:
- 头信息Index Head部分:主要记录整个文件的相关信息
beginTimestamp
:第一个索引消息落在Broker的时间戳;endTimestamp
:最后一个索引消息落在Broker的时间戳;beginPhyOffset
:第一个索引消息在commitlog的偏移量;endPhyOffset
:最后一个索引消息在commitlog的偏移量;hashSlotCount
:构建索引占用的槽位数;indexCount
:构建的索引个数;
- Hash槽 Slot Table 部分:保存的是消息key在Index部分的位置,槽位的确定方式是消息的topic和key中间用#拼接起来(topic#key)然后对总槽树取模,计算槽位。
- Index链表部分:Index中存储的是消息相关的详细信息,和hash冲突时的处理方式
keyHash
:topic#key结构的Hash值(key是消息的key)phyOffset
:commitLog真实的物理位移timeOffset
:时间位移,消息的存储时间与Index Header中beginTimestamp的时间差slotValue
(解决hash槽冲突的值):当topic-key(key是消息的key)的Hash值取500W的余之后得到的Slot Table的slot位置中已经有值了(即Hash值取余后在Slot Table中有hash冲突时),则会用最新的Index值覆盖,并且将上一个值写入最新Index的slotValue中,从而形成了一个链表的结构。
IndexFile文件相关的类
IndexFile头文件相关的IndexHead
类
IndexHead
类关联的其实就是IndexFile文件的头文件相关的信息,没有复杂的方法,都是一些字段的get和set方法
//IndexFile的头大小
public static final int INDEX_HEADER_SIZE = 40;
//beginTimestamp:第一个索引消息落在Broker的时间戳
private static int beginTimestampIndex = 0;
//endTimestamp:最后一个索引消息落在Broker的时间戳
private static int endTimestampIndex = 8;
//beginPhyOffset:第一个索引消息在commitlog的偏移量;
private static int beginPhyoffsetIndex = 16;
//endPhyOffset:最后一个索引消息在commitlog的偏移量;
private static int endPhyoffsetIndex = 24;
//hashSlotCount:构建索引占用的槽位数
private static int hashSlotcountIndex = 32;
//indexCount:构建的索引个数
private static int indexCountIndex = 36;
//记录对应信息用的原子类
private AtomicLong beginTimestamp = new AtomicLong(0);
private AtomicLong endTimestamp = new AtomicLong(0);
private AtomicLong beginPhyOffset = new AtomicLong(0);
private AtomicLong endPhyOffset = new AtomicLong(0);
private AtomicInteger hashSlotCount = new AtomicInteger(0);
private AtomicInteger indexCount = new AtomicInteger(1);
可以看到这里通过6个字段来表示对应的6个字段的偏移量,其中6个字段的值都是用原子类来记录表示的。
IndexFile读写相关的IndexFile
类
IndexFile文件相关操作对应的就是IndexFile
类,提供对IndexFile文件的插入信息和对应的查询操作。
字段属性
//hash曹的大小
private static int hashSlotSize = 4;
//一个index结构的大小
private static int indexSize = 20;
//无效的index
private static int invalidIndex = 0;
//hash槽总数
private final int hashSlotNum;
//index的数量
private final int indexNum;
//IndexFile文件的映射文件对象
private final MappedFile mappedFile;
private final FileChannel fileChannel;
private final MappedByteBuffer mappedByteBuffer;
//IndexFile的头信息
private final IndexHeader indexHeader;
记录的主要是整个文件中相应的单元的单个大小,和对应hash槽和index链表的大小。和对应文件的映射对象等信息
内部方法分析
构造方法
构造方法主要就是对应的主要参数的设置,根据入参计算整个文件的大小(IndexFile的头大小 + hash槽的大小 x hash槽的数量 + index结构的大小 x index结构的数量),然后创建文件,设置文件头对象IndexHead
public IndexFile(final String fileName, final int hashSlotNum, final int indexNum,
final long endPhyOffset, final long endTimestamp) throws IOException {
//计算文件的大小 = IndexFile的头大小 + hash槽的大小*hash槽的数量 + index结构的大小*index结构的数量
int fileTotalSize =
IndexHeader.INDEX_HEADER_SIZE + (hashSlotNum * hashSlotSize) + (indexNum * indexSize);
//获取映射文件对象
this.mappedFile = new MappedFile(fileName, fileTotalSize);
//获取对应的channel
this.fileChannel = this.mappedFile.getFileChannel();
//获取对应文件的缓存
this.mappedByteBuffer = this.mappedFile.getMappedByteBuffer();
//设置hash槽数量
this.hashSlotNum = hashSlotNum;
//设置index结构的数量
this.indexNum = indexNum;
ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
//创建文件对应的IndexHead对象
this.indexHeader = new IndexHeader(byteBuffer);
//初始化头文件的beginPhyOffset 和 endPhyOffset
if (endPhyOffset > 0) {
this.indexHeader.setBeginPhyOffset(endPhyOffset);
this.indexHeader.setEndPhyOffset(endPhyOffset);
}
//初始化头文件的beginTimestamp 和 endTimestamp
if (endTimestamp > 0) {
this.indexHeader.setBeginTimestamp(endTimestamp);
this.indexHeader.setEndTimestamp(endTimestamp);
}
}
保存key对应index的putKey
方法
这个方法的调用,是在消息存入CommitLog之后,进行消息转存的时候会调用。这里简单贴一些调用链。
ReputMessageServ