RocketMQ之indexfile

这是索引文件(磁盘文件),文件命名规则:${storePath}/index/${timestamp}

作为一名优秀的桥梁,主要是存储消息索引key和消息物理偏移量offset索引的对应关系,简单说就是标记这个key在什么位置,方便快速定位commitlog消息,方便消费者消费,上帝自然要"伺候"到位了。

提供了一种通过key或时间区间来查询消息的方法

这究竟是怎么做到的呢?

首先还是从结构上说,文件名是创建时的时间戳,文件固定大小400M,优秀的设计智慧让一个indexfile可以保存2000w个索引,2000w呢!这你敢想?你是不是也有疑问,为什么能存这么多呢,大数量就要看底层了,咱们底层存储用的是map(所以rocketmq索引文件底层实现是hash索引)

复制代码
+---------------------------------------------------------------+
|                        IndexFile (≈400MB)                     |
+-----------------------+---------------------------------------+
|      Header (40B)                 |                                        |
|-----------------------------------|     Hash Slot Table 20M 500w个int      |
| beginTimestamp最早消息的时间戳(8B)   |  +----+----+----+----+ ... +----+      |
| endTimestamp最晚消息时间戳   (8B)    |  | 0  |8872| 0  |1024|     | 0  |      |
| beginPhyOffset最早消息在commitlog中偏移量8B |  +----+----+----+----+ ... +----+      |
| endPhyOffset最晚消息在commitlog中偏移量8B   |    ↑槽int值,>0指向indexLinkedList的序号  |
| hashSlotCount当前已使用hash槽数 4B   |    └── slot[123456] = 8872             |
| indexCount当前已写入的索引条数 (4B)   |                                        |
+-----------------------+---------------------------------------------------------------------------------+
|                      Index Linked List 2000w条目                              |
|  +-----------------------------------------------------------------------------------------------------------------+
|  | Entry #1: keyHash=..., phyOffset=..., timeDiff=..., prevIndex=0 | 20B ← 链尾
|  | Entry #8872: keyHash=..., phyOffset=123456789, timeDiff=3600, prevIndex=1024 |
|  | Entry #1024: keyHash=..., phyOffset=123450000, timeDiff=3500, prevIndex=0    |
|  +----------------------------------------------------------------------------------------------------------------+             
|  ↑                                                                              |
|  └── slot[123456] 指向 #8872,形成链表:8872 → 1024 → 0                            |
+---------------------------------------------------------------------------------------------------------------------+

上面还是不太方便看,简单介绍一下:

1、首先总的来说得有个概览,欸~indexhead 头就是一个概览,对吧,我当前的indexfile有什么,作为head有义务都给咱indexfile记下,比如最早几点来 的消息,最晚几点结束 的消息;咱们都知道,这生产者producer生产的消息来了肯定都不白来,只要你来我"跑着"去见你,咱就甭管是是什么,commitlog都会给你出位置、让你放;那么多消息放在哪是一个巨大的问题,当然rocketmq有很多解决方案,首先我索引文件必须名副其实,积极表现、麻溜就给你标记上,怎么记?首先我的头indexhead首当其冲,先把最早的那条、最晚的那条消息的偏移量、已经写入了多少条的索引给你记上,这样定一个基调也明确了责任,就是出了这范围咱就是那渣男------概不负责!你也别说我偷懒,总共给我40b位置,多了我也记不下,而且重要的hash槽,他用了多少了、我也得记着,方便下一个indexfile兄弟用,毕竟我们才是用"同一磁盘的血脉至亲"!再说你放心、剩下的我们hash槽兄弟会出手

2、hashSlotTable固定500万大军(哈希槽)每个槽4字节;先介绍他其实有点费劲,为什么我们head可怜兮兮20b还要要记录当前已经使用的hash槽数,因为每个槽存储的最新插入的索引项在索引区中的位置,说白了还是一个"索引",一共20M

https://cloud.tencent.com/developer/article/1850208

3、刚提到索引项,她就来了,索引项区indexLinkedList最多支持2000w索引项,每个20字节,和head比都是土豪了,这么多是用来存什么呢?不要着急,首先hashcode存储消息key的hashcode值方便去重定位,phyoffset这个好猜吧,消息的物理偏移量!这个就很重要了,自报家门、也不是自报,反正这不就是把人家家门都记得清清楚楚了!timeDiff相对于beginTimestamp的时间差(s)就说这个消息距离第一条消息已经过去了多长时间(大概这个意思吧);prevIndex这个很重要了,这是同一个哈希槽下的前一个索引项的位置(反向指针 ),前一个索引项的位置 ,这样就是一个反向链 表了!这设计真是天才啊,这样即使key冲突了,可以通过prevIndex反向链接解决,就是说当多个key映射到同一个slot时,他们不会覆盖而是通过prevIndex形成了一个时间倒序链表!查询到时候定位到这个链,咱们拿着hashcode和时间范围,在链表一个一个比较就能找到对的那个人、那个数值

复制代码
slot[1001] → [Index#5] → [Index#3] → [Index#1] → null
               ↑          ↑          ↑
           prev=3      prev=1      prev=0

当然链表太长了,水满则溢,也不好:影响查询性能

两个结合看吧,大概是这么个意思,有不对的欢迎大佬指正

流程

上面说的虽然很精彩(嘘~),但是还不够,有些懵懵懂懂的,这可不太好

写入

当一条消息写入commitlog后,如果enableIndexWrite=true并且消息有n个key,则

  1. 计算key的hash值,就是keyhash(indexlinkedList索引项区中)
  2. 找到自己的位置:计算槽位slotIndex:int slotIndex = keyHash % hashSlotNum; // hashSlotNum = 5,000,000;os 人这一生清楚自己的位置,知道自己的定位很重要,我得向咱们rokcetmq多学习;
  3. 找到前任的位置:如果有的话获取上一个slot值prevIndex:int prevIndex = this.indexHeader.get(slotIndex);os所以人这一生搞好邻里关系同样重要,万一有个什么事,这都是很好的关系,为你遮风挡雨、为你kuku躲避哈希冲突;
  4. 真正弄自己的位置:首先phyOffset(自己的本我在commitLog中的哪里);timeDiff距离开始现在已经过去了多少秒,会不会太久而让老大生气;prevIndex上一步获取的值的位置
  5. 尘埃落定:写入索引区IndexLinkedList新索引项追加到索引区末尾:当前 indexCount + 1,更新 slot[slotIndex] = newIndexPos,正式入驻
  6. 哦还有header,这个老大哥终于出现了,更新 endTimestamp结束的时间戳endPhyOffset最后的物理偏移量,看来随着这新一条的加入大家迎来新的面孔,当然indexCount也得++当前已写入的索引条数喜加一条,所以咱就是说关键时刻在人家地盘上一定得报备或得官方承认;当然如果是第一个消息,也得更新新 beginTimestampbeginPhyOffset,可以看得出我这一生如履薄冰、尽心尽责、思虑周全

每条带key的都出发索引写入,增加了IO,毕竟是磁盘文件;不过好在indexFile 异步构建的(由后台线程处理 PutRequestQueue)问题不大。

查询

queryMessage(key, maxNum, begin, end)

  1. 自然先遍历indexFile(倒序从新到旧)利用begin/endTimestamp 快速跳过不匹配的,看到没都是有用的,而且是大用处,不可小觑
  2. 找到了indexFile,计算slotIndex找到槽slotIndex = key.hashCode() % 5,000,000,读取slot[slotIndex] 得到第一个索引项位置 indexPos,沿着 prevIndex 链表反向遍历:hashcode 是否匹配,timeDiff + beginTimestamp 是否在 [begin, end] 范围内,将 phyOffset 加入结果集
  3. 达到maxNum的限制,人不能太贪,主要是太多重了------拿不走
  4. 根据phyOffset从commitlog中获取信息

大家也能看粗来,这key都是精确匹配的,不能模糊查询,要什么自行车

大概是这样,是不是很智慧,现在满屏扣智慧,谢谢大家的阅读,请多多指教!

相关推荐
大厂技术总监下海11 小时前
大数据生态的“主动脉”:RocketMQ 如何无缝桥接 Flink、Spark 与业务系统?
大数据·开源·rocketmq
自燃人~1 天前
RocketMQ 架构与设计原理
架构·rocketmq
星辰_mya3 天前
rocketMQ之ConsumeQueue
rocketmq
用户0203388613143 天前
RocketMQ知识点梳理
rocketmq
sww_10264 天前
Kafka和RocketMQ存储模型对比
分布式·kafka·rocketmq
星辰_mya5 天前
rocketMQ的消息存储CommitLog
rocketmq
虎啊兄弟6 天前
RocketMQ面试题
数据库·rocketmq
予枫的编程笔记6 天前
深度解析Apache RocketMQ:从核心原理到实战应用
java·apache·rocketmq
Psycho_MrZhang6 天前
RocketMQ 设计思想总结
rocketmq