RocketMQ源码解析-消息是如何写入Broker服务器(客户端篇)
RocketMQ源码解析-消息是如何写入Broker服务器(Broker端篇)
RocketMQ源码解析-步步拆解ConsumeQueue的写入流程
在以上的RocketMQ源码解析篇中,我们梳理了一条消息时如何由客户端发往Broker端的,在到达Broker端后,消息经过处理是如何存入commitlog、consumeQueue对应文件的。然而,这条消息的最终流转还没有结束,还有一个indexFile文件没有写入。
本篇文件,我们将重点解析上图中标注红框的部分,也就是站在源码的角度来梳理一下indexFile文件的内容是如何被构建出来的。
在源码解析之前,首先对indexFile文件做一个整体的介绍。
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的入参一致。
buildInde方法隶属于IndexService类,IndexService封装了关于indexFile文件的所有操作,其基本属性如下。
buildIndex方法作为消息写入indexFile文件的入口方法,主要做了如下几步:
1.先获取待写入的indexFile文件
2.对要写入的消息做一下写入前的校验,排除不要写入的消息。
3.将uniqKey作为索引key写入到indexFile文件,uniqKey是在消息发送客户端构建的,每天消息都存在。
4.根据设置的消息Keys属性,构建索引key,写入到indexFile文件。消息Keys属性默认是没有的,需要人工添加。
关注一下getAndCreateLastIndexFile这个方法,这个方法主要是获取待写入的indexFile文件,若获取到的indexFile文件写满了,则创建新的indexFile文件,将写满的indexFile文件在新创建的线程中刷新到磁盘上去。
这里说明下两点,第一点是indexFile文件写满的标志是indexNum索引条目的数量等于系统设定的值,默认2000万,另一点是indexFile文件的刷盘动作只有在旧文件写满后在新创建一个indexFile文件的时侯触发旧文件的刷盘动作。
putKey方法会重试的将idxKey和DispatchRequest写入到indexFile文件
核心写入方法,将indexkey,phyoffset,storeTimesTamp写到indexFile中,返回值为true表示写入成功,false则会继续写入。
值得提示的是,一个indexFile实例逻辑上表示一个物理磁盘文件。
至此一条消息在Broker服务器涉及的文件写入已经梳理完了。
对整个RocketMQ的写入做个总结
总的来说,一条消息先是写入到了commitlog文件,然后由异步线程转发写入了consumeQueue以及indexFile文件,commitlog文件的写入提供了两种方式,一种是写堆外内存通过mmap读pageCache的模式,一种是通过mmap读写PageCache的模式。而consumeQueue以及indexFile只涉及通过mmap的方式与pageCache交互。而且不同文件磁盘的持久化机制也是不同的。