RocketMQ消息写入-步步拆解IndexFile的写入流程

541 阅读5分钟

RocketMQ源码解析-消息是如何写入Broker服务器(客户端篇)

RocketMQ源码解析-消息是如何写入Broker服务器(Broker端篇)

RocketMQ源码解析-步步拆解ConsumeQueue的写入流程

在以上的RocketMQ源码解析篇中,我们梳理了一条消息时如何由客户端发往Broker端的,在到达Broker端后,消息经过处理是如何存入commitlog、consumeQueue对应文件的。然而,这条消息的最终流转还没有结束,还有一个indexFile文件没有写入。

image.png

本篇文件,我们将重点解析上图中标注红框的部分,也就是站在源码的角度来梳理一下indexFile文件的内容是如何被构建出来的。

在源码解析之前,首先对indexFile文件做一个整体的介绍。

image.png

indexFile文件总体由三部分构成:

第一部分为文件头(Header)总共占用40个字节。构成部分为beginTimestamp、endTimestamp、beginPhyOffset、endPhyOffset、hashSlotCount、indexCount。

beginTimestamp表示的是转发到indexFile文件中第一条消息在存储commitlog时被赋予的存储时间戳。

endTimestamp表示当前追加进入indexFile文件消息在存储commitlog时被赋予的存储时间,会不断的被更新。

beginPhyOffset表示表示的是转发到indexFile文件中第一条消息在存储commitlog时字节物理偏移量。

endPhyOffset表示当前追加进入indexFile文件消息在存储commitlog时字节物理偏移量,会不断的被更新。

hashSlotCount表示当一个新的哈希槽(hashSlot)被使用时进行加一统计。目前这个字段只做统计,其他没有被使用的地方。

indexCount表示indexFile文件索引条目使用的计数,每写入一条消息就自增一,最大为2000W个。

第二部分为SlotTable,默认值为500W个。单个hashslot占用4个字节,总共占用2000W字节。

具体写入hashslot的方式为消息的key进行哈希取值后与500W个hash槽取余获取的值就是写入的位置,即 (int slotPos = keyHash % 500W),写入的值是最新获取的index索引条目。

第三部分为indexNum索引条目,总计2000W个索引条目。每新写一条消息,索引条目的使用数自增一(即indexCount的值自增一),当indexCount值等于2000W时,则新创建一个indexFile文件进行写入。索引条目写入一条数据占用20个字节,主要包括keyHash、phyOffset、timestamp、NextIndexOffset。

keyHash表示的是消息索引key的hash值,RocketMQ之所以存储hash值是为了保障存储索引条目的数据大小是一致的,但是增加了查询的复杂度,因为索引key的hash值可能存在重复。

phyOffset表示的是消息在commitlog中全局物理字节偏移量,用phyOffset可以定位到具体的消息。

timestamp表示是当前消息的存储时间戳与indexFile文件头中存储的beginTimestamp差值,单位为秒。

NextIndexOffset表示是当前这条消息哈希到该hashslot处,该hashslot上一个存储的index索引条目。这个字段的存在,构成了文件存储的链式结构。根据上一个index的索引条目就能够定位到该index索引条目存储的数据信息,NextIndexOffset为0表示没有下一个节点了,链式结构到尾部了。

以上篇幅对indexFile文件各组成部分做了一个整体的介绍。接下来,以源码视角看一下数据是如何写入到indexFile文件中去的。

接下来,开启本篇的源码解析部分

dispatcherList中有两个对象实例,CommitLogDispatcherBuildConsumeQueue是用于构建consumeQueue的,上一篇文件中已经梳理过了。CommitLogDispatcherBuildIndex是构建indexFile的,本篇文件以它作为入口。

可以看到当isMessageIndexEnable为true的时候(默认为true),调用了indexService.buildIndex方法,方法入参为DispatchRequest,与构建consumeQueue的入参一致。

image.png

image.png

buildInde方法隶属于IndexService类,IndexService封装了关于indexFile文件的所有操作,其基本属性如下。

image.png

buildIndex方法作为消息写入indexFile文件的入口方法,主要做了如下几步:

1.先获取待写入的indexFile文件

2.对要写入的消息做一下写入前的校验,排除不要写入的消息。

3.将uniqKey作为索引key写入到indexFile文件,uniqKey是在消息发送客户端构建的,每天消息都存在。

4.根据设置的消息Keys属性,构建索引key,写入到indexFile文件。消息Keys属性默认是没有的,需要人工添加。

image.png

关注一下getAndCreateLastIndexFile这个方法,这个方法主要是获取待写入的indexFile文件,若获取到的indexFile文件写满了,则创建新的indexFile文件,将写满的indexFile文件在新创建的线程中刷新到磁盘上去。

这里说明下两点,第一点是indexFile文件写满的标志是indexNum索引条目的数量等于系统设定的值,默认2000万,另一点是indexFile文件的刷盘动作只有在旧文件写满后在新创建一个indexFile文件的时侯触发旧文件的刷盘动作。

image.png

putKey方法会重试的将idxKey和DispatchRequest写入到indexFile文件

image.png

核心写入方法,将indexkey,phyoffset,storeTimesTamp写到indexFile中,返回值为true表示写入成功,false则会继续写入。

值得提示的是,一个indexFile实例逻辑上表示一个物理磁盘文件。

image.png

至此一条消息在Broker服务器涉及的文件写入已经梳理完了。

对整个RocketMQ的写入做个总结

总的来说,一条消息先是写入到了commitlog文件,然后由异步线程转发写入了consumeQueue以及indexFile文件,commitlog文件的写入提供了两种方式,一种是写堆外内存通过mmap读pageCache的模式,一种是通过mmap读写PageCache的模式。而consumeQueue以及indexFile只涉及通过mmap的方式与pageCache交互。而且不同文件磁盘的持久化机制也是不同的。