RocketMQ源码解析之broker消息存储流程(BuildIndex)

原创不易,转载请注明出处

消息写入流程系列文章列表

  1. RocketMQ源码解析之broker消息存储流程(PutMessage)
  2. RocketMQ源码解析之broker消息存储流程(Reput ConsumeQueue)
  3. RocketMQ源码解析之broker消息存储流程(BuildIndex)


前言

我们在《RocketMQ源码解析之broker消息存储流程 》的其他文章中分别介绍了Put Message 与Reput ConsumeQueue的流程以及剖析源码,其中在《RocketMQ源码解析之broker消息存储流程(Reput ConsumeQueue)》一文中,提到RocketMQ存储器初始化的时候默认创建好两个dispatch塞到dispatcherList中,在Reput的时候,每获取到一条消息的信息,就会遍历dispatcherList集合交给每个dispatcher进行dispatch,关于BuildConsumeQueue的dispatcher在上文已经介绍过了,本文主要是介绍一下另一个dispatcher BuildIndex 的工作流程,并进行一个深入源码的剖析。

1. BuildIndex原理介绍

首先解释下BuildIndex 这个词是什么意思,其实就是一个创建索引,构建索引的意思,我们知道当消息生产者send一条消息给broker ,broker 先是会将消息存入commitlog中,并将消息存入结果返回给消息生产者, 然后后台有一个reput的线程,不断的从commitlog中取出消息来,交给不同的dispatcher来进行处理,其中有BuildConsumeQueue这么一个dispatcher,拿到消息的信息后,按照消息不同topic 不同的queue 找到对应的ConsumeQueue,然后将消息的在commitlog中的一个offset,消息的大小,消息tagcode 根据queue offset 写到consumeQueue对应的位置中,这样做的对于消息消费者端只需要知道 从哪个topic ,哪个queue,哪个位置(queue offset )开始消费就可以了,通过这个几个元素,就可以获取对应消息在commitlog 的offset ,消息的大小,然后拿着commitlog offset与消息大小,就可以到commitlog获取到完整的消息。通过上面的介绍我们知道了consumeQueue 是给消息消费者进行消费使用的,那么我们构建索引是干什么用的呢?比如说,我们想看看某个消息的被哪个消息消费者给消费的,或者是消息丢了,我们看看这个消息有没有被存到broker 上面,我们可以通过RocketMQ提供的可视化界面根据消息的topic,msgId或者topic,key进行查找,找到你想要的消息,BuildIndex你可以理解为往HashMap put 元素,它会根据 消息的topic与msgId 或者是topic与key 生成一个 key,然后将那个消息的commitlog的offset,key的hash值等元素封装成一个value,写到indexFile中。
在介绍写入过程之前我们要知道RocketMQ中它存放索引的文件是有多个的,由indexFileList 这个arrayList存着,然后一个索引文件就是一个indexFile,一个indexFile就对应着一个MappedFile,一个MappedFile就对应着一个文件,一个索引文件中默认是有500w个hash槽,2000w个索引,还有文件头,一个文件头是40个字节,一个hash槽是4个字节,一个索引是20字节,一个indexFile大约就有400多m。
当一个消息的信息dispatch过来,先是找到一个indexFile,就是从indexFileList这个集合中找最后一个,如果是满了的话就创建一个,根据消息的topic与msgId生成一个key,计算出这个key的hash值,计算出这个hash值哪个hash槽上面,根据hash槽找到具体的位置,然后获取里面的值,如果hash槽里面有值了,这个时候就说明之前有消息挂在这个hash上了,就跟我们HashMap的hash冲突一样,封装数据的时候会把上一个hash槽里面的值封装进去,然后将封装的数据存起来,在索引文件头也就是indexFileIndex 中维护了一个indexCount来记录当前索引文件的数量,然后你每添加一个索引,就会自+1,你也可以理解indexCount 是当前索引在索引文件中的位置,事实上,索引在文件的位置就会这个值算出来的。hash槽里面存的也是这个值,当你封装好你索引数据并写入的时候,也会将hash里面的值改成你这个indexCount值。
下面我贴上一张流程图
在这里插入图片描述
(原图请访问Process on:链接 支持免费克隆)
其实这个样子就可以看到,当hash冲突的时候,跟jdk1.8之前的HashMap差不多一样的解决方案,就是挂链表,就是上图虚线这个样子。
需要注意的是,它不光根据你topic与msgId创建索引,还会根据你topic与keys创建索引,那个keys就是你消息生产者在发送消息之前封装Message的时候塞进去的,我们可以看下最佳实践关于keys的描述
在这里插入图片描述
(截图来源:链接

2. BuildIndex源码解析

这一小节我们将看看BuildIndex的源码,我们先是从这个dispatch开始:
在这里插入图片描述
我们可以看到是调用了IndexSerivce的buildIndex方法来构建索引,接着再来看下这个buildIndex 方法
在这里插入图片描述
重点关注红框里面内容,先是调用retryGetAndCreateIndexFile方法获取一个IndexFile,接着就是获取消息里面的topic,keys,事务状态,如果是回滚事务就不需要创建索引了,接着就是构建一个topic#msgId的索引了,最后是构建topic#key的索引(我们在塞入keys的时候如果是一个key的话不要有空字符,不然的话它就会当成2个key,然后进行分割),先来看下这个获取indexFile的retryGetAndCreateIndexFile 方法:
在这里插入图片描述
该方法就是带有重试获取的方法,重试次数是3,就是调用了getAndCreateLastIndexFile方法来获取,我们看下这个方法,这个方法很长,我们就挑重点的解释
在这里插入图片描述
先是从indexFileList中获取最后一个indexFile,然后判断满了没满,如果是满了的话,就要重新建了
在这里插入图片描述
这一段代码其实就是新建一个indexFile,然后创建线程把上一个的indexFile进行刷盘,好了这里我们获取indexFile的源码就解释清楚了,接着看下putKey的方法,这个方法就是失败重试的方法。在这里插入图片描述
我们可以看到有个循环一直重试,失败就是重新获取一个indexFile,然后再进行putKey,下面就是具体过程了,与我们上图介绍的流程差不多
在这里插入图片描述
这一段是先判断一下这个indexFile 中的index有没有超,默认是2000w个索引,如果是超了的话,就返回false,到上一层方法中就会重新获取indexFile,重新putKey,我们这里就是假设它没有超,接着就是 根据key计算一个hash值出来,计算过程:
在这里插入图片描述
就是String的hashCode()方法,然后取的绝对值。接着就是使用hash值% hash的数量,就能得到这个index在哪个hash槽上面,然后在计算hash在文件中的位置。
在这里插入图片描述
接着就是根据hash槽在文件中的位置,获取hash槽里面的那个值,其实hash槽里面存的是索引的index,也就是这个文件的第几个索引,是维护在header头里的一个成员变量,这里这个invalidIndex是0 ,其实就是判断从hash槽里面取出来的值合不合法,不合法就设置成0,下面这段就是计算当前要构建index的这个消息 写入commitlog时间距离 这个索引文件第一个消息写入commitlog的一个时间差
在这里插入图片描述
接着这一部分先是计算出 这个索引应该存在这个索引文件的什么位置header大小(40)+ hash槽数量(500w)* 一个hash槽大小(4)+index(当前这个索引在这个索引文件中是第几个,第一个存入的就是1,第二个存入的就是2 )* 一个索引的大小(20),计算好位置后,就进行追加写,先是4个字节的keyhash值,接着就是8个字节消息在commitlog的offset,接着4个字节与第一个索引写入commitlog的一个时间差,最后就是4个字节的 hash槽上一个索引的index值,这个非常重要,这个样子,hash冲突就是链表的形式解决的。
接着又将 这个索引的index值写入 hash槽中。
最后这一堆其实就是更新索引文件头header的里面的数值,先是判断是不是第一个索引,第一个索引的话,设置开始的一个commitlog offset与开始的一个写入时间。接着hash槽+1,index数量+1,更新最后的commitlog offset 与最后一个索引的写入commitlog的时间。
到这里BuildIndex源码解析就结束了,是不是跟HashMap的put方法很像呢,可以结合上面的流程图来理解这些代码。

总结

本篇主要是介绍了一下BuildIndex的工作原理与解析其源码。可以把BuildIndex的过程理解成HashMap的一个put流程,本文还没有说为啥是索引的呢,其实从写入索引的字段就能知道 ,里面有个commitlog 的offset ,你通过这个offset,就能从commitlog中快速找到这个消息,当我们要找某个key对应的消息的时候,总不能从commitlog中直接去翻吧,那个样子,成本太高了,通过计算key找到对应hash槽,然后找到hash槽里面的索引,看看是不是你要找到那个hash值,不是的话,就去找这个索引的最后一个字段,也就是上一个index,继续往下找,最后肯定能找到,实在找不到就是没有,找到之后,就能找到对应的commitlog offset ,就可以通过这个offset 快速定位到在哪个MappedFile里面的哪个位置,然后就能快速读出来了。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页