RocketMQ源码解析——存储部分(5)IndexFile消息索引日志文件相关的`IndexService`类

本文深入剖析RocketMQ的IndexFile,介绍其结构、作用及关键类。IndexFile通过key或时间查询消息,采用hash索引设计,提升检索效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值