写点什么

🏆【Alibaba 中间件技术系列】「RocketMQ 技术专题」让我们一同来看看 RocketMQ 和 Kafka 索引设计

作者:浩宇天尚
  • 2022 年 1 月 13 日
  • 本文字数:2177 字

    阅读完需:约 7 分钟

🏆【Alibaba中间件技术系列】「RocketMQ技术专题」让我们一同来看看RocketMQ和Kafka索引设计

背景介绍

文件索引,是存储设计的关键,一个好的索引,应该能够在最短的时间里,找到你想要的数据,同时,还能尽量少的使用内存或磁盘空间。


但是这里说的索引并不是指 MySQL 或者 NoSQL 这些数据库索引,而是 MQ 中间件的索引。相对而言较为简单的 MQ 索引。我们可以通过研究 MQ 的索引,看看他们为何如此设计,我们又有哪些借鉴之处,并且也可以根据他们索引文件的设计模式,进行分析他们的性能问题,接下来我们借来分别说说 RocketMQ 和 Kafka 的索引设计原理,重点我们会介绍 RocketMQ 的设计。

RocketMQ

相比较 Kafka 的分区索引文件的设计方案,RocketMQ 的数据文件属于混合存储,即,所有的 topic 数据都放在一个文件里,因此,读数据的时候,就无法做到连续读了,只能随机读。


所以,RocketMQ 推荐使用大内存,利用 PageCache 预读机制把 commitlog 数据缓存起来,混合存储的好处则是能够承受万级别的队列数量


kafka 64 分区有些夸张,单机单磁盘 1000 分区还是没啥问题的,经验之谈最好别超过 2000,


RocketMQ 提供基于 MsgID 搜索消息的方案,即,每条消息,都有一个唯一的 ID,

Message ID

ID 由 broker IP + Port + CommitLog Offset 组成,通过这两个参数,可快速定位到一条消息。注意,Kafka 是没有这个功能的,但理论上,通过 Kafka 的 offset 也是可以找到具体的消息的。

另外 RocketMQ 有 2 种索引。

  • 消息消费索引

  • Hash 查询索引

消息消费索引

消息消费索引,可以理解为,就是 topic 的索引数据,类似 kafka 的索引数据。如果没有这个,消费者基本就找不到消息了。这个索引里,存放着对应 topic 、对应 queue 里的消息连续 offset 集合(不像 commitLog 是混合存储的)。


RocketMQ 的存储层架构
RocketMQ 的运作流程图
RocketMQ 的存储设计图:

消息被不停的 append 到 commitlog,然后,再构建消费索引,如果没有这个索引,consumer 要在 commitlog 里消费消息,那可真是太难了。



每个 consumerQueue 文件里存放着 30w 个元素,每个元素 20 字节,8 字节 offset ,4 字节 size, 8 字节 tag hashcode,因此,每个文件也就 5.8MB 不到,很轻量。


Hash 查询索引(我们可以称之为 tag)

Hash 查询索引,主要是根据 Key 来快速查询消息,属于一种附加功能。RocketMQ 采用了 Java HashMap 的思想,实现了 Hash 索引的存储。


  • 如果这个 Map 有 500w 个 slot,每个 slot 的链表长度为 4. 如果我们使用一个 key 进行消息查找,他的过程是这样的:先 hash key 得到 hashCode,然后对 500w 取余,找到槽位,这个槽位大小是 4 个字节,保存了链表尾部的具体元素地址。

  • 而这个链表元素的大小是 20 个字节,保存了 key 的 hash 值,commitlog offset,时间戳,还有他下一个链表节点的地址。

  • 为什么在 链表元素里保存 了 hash 值呢?为了防止 hash 值不同,但是 hash 取模后的结果相同(也就是 hash 冲突),如果冲突了,就用 hash 值比对一下。

  • 那如果 hash 值相同,key 内容不同呢?RocketMQ 的做法是放在客户端过滤。

简单介绍一下 Kafka

Kafka 每个 topic 有多个 partition ,每个 partition 有多个 segment,每个 segment 里,存储了消息的相关文件:数据文件,索引文件。


Kafka 不像 RocketMQ,所有数据都存在一个文件里,Kafka 每个 topic 的文件都是隔离开的,而每个 topic 又可能会有很多的 partition(看你的配置),因此,如果你的 topic 非常多,或者你的 partition 非常多的话,顺序写就会变成随机写,性能会骤降。

Kafka 的索引文件和数据文件绑定在一起的。

与 RocketMQ 的消费索引类似,Kafka 里面是逻辑 offset 映射物理 offset ,并且采用了稀疏索引的方式。然后,我们看看他们的索引设计,如下图:


[逻辑索引,偏移量]


  • 逻辑索引,即这个 partition 下的全局递增逻辑索引(当然,这个是相对偏移量,这里为了描述简单,就不区分了)

  • 偏移量,表示这条消息的所在文件的物理 position。


我现在是一个消费者,订阅了这个 partition 的消息,那么我将从 0 号逻辑索引开始订阅,从.index 开始遍历,然后找到对应的物理文件 position。


kafka 的这个 .index 文件和 RocketMQ 的 consumerQueue 索引很相似,直接遍历 .log 文件,从头开始消费。但如果,我不想从头开始消费呢?我想从第 18 条消息开始消费呢?因为没有 .index ,我只能慢慢遍历。


一个 topic 设计一个递增的 offset,从 0 开始,每新增一条消息,加一。这是一个逻辑偏移量,我们让逻辑偏移量 映射 物理偏移量。消费者也从 0 开始消费,这样,就达到了某种默契。就算是第 18 条消息,我也能快速找到。


基于 partition 的分区原子计数器。使用 broker ID + 分区 ID + 计数器 就可以标识一条唯一的消息。然后,用计数器映射 偏移量 offset,简直就是完美。然后,为了达到搜索效率和空间消耗的平衡,边稠密索引为稀疏索引。

RocketMQ 和 Kafka 的索引设计相似之处:

RocketMQ 的 topic 和 kafka 的 topic 类似,RocketMQ 的 queue 和 kafka 的 partition 类似,都是为了 scale out。


  • RocketMQ 为每个 queue 设计了 consumerQueue 索引文件,每个文件大小固定 5.8MB;

  • Kafka 为每个 partition 设计了 segment (.index + .log)。


consumerQueue 索引文件和 segment 的 .index 本质是一样的,都是为了让 consumer 快速找到消息。

和 Kafka 的索引设计的最大不同

RocketMQ 是所有 topic 混合存储,目的是支持更多的 topic,而 Kafka 的 topic 是单独存储,好处是顺序读性能好,另外,根据分区做副本也比较好做。

发布于: 刚刚阅读数: 2
用户头像

浩宇天尚

关注

🏆InfoQ写作平台-签约作者🏆 2020.03.25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
🏆【Alibaba中间件技术系列】「RocketMQ技术专题」让我们一同来看看RocketMQ和Kafka索引设计