共计 3249 个字符,预计需要花费 9 分钟才能阅读完成。
RocketMQ 优异的性能体现,必然绕不开其优良的存储模型。
这篇文章,笔者依照本人的了解 , 尝试剖析 RocketMQ 的存储模型,心愿对大家有所启发。
1 整体概览
首先复习下 RocketMQ 架构。
整体架构中蕴含四种角色 :
Producer:音讯公布的角色,Producer 通过 MQ 的负载平衡模块抉择相应的 Broker 集群队列进行音讯投递,投递的过程反对疾速失败并且低提早。
Consumer:音讯生产的角色,反对以 push 推,pull 拉两种模式对音讯进行生产。
NameServer:名字服务是一个非常简单的 Topic 路由注册核心,其角色相似 Dubbo 中的 zookeeper,反对 Broker 的动静注册与发现。
BrokerServer:Broker 次要负责音讯的存储、投递和查问以及服务高可用保障。
本文的重点在于剖析 BrokerServer 的音讯存储模型。咱们先进入 broker 的文件存储目录。
音讯存储和上面三个文件关系十分严密:
数据文件 commitlog
音讯主体以及元数据的存储主体;
生产文件 consumequeue
音讯生产队列,引入的目标次要是进步音讯生产的性能;
索引文件 index
索引文件,提供了一种能够通过 key 或工夫区间来查问音讯。
RocketMQ 采纳的是混合型的存储构造,Broker 单个实例下所有的队列共用一个数据文件(commitlog)来存储。
生产者发送音讯至 Broker 端,而后 Broker 端应用同步或者异步的形式对音讯刷盘长久化,保留至 commitlog 文件中。只有音讯被刷盘长久化至磁盘文件 commitlog 中,那么生产者发送的音讯就不会失落。
Broker 端的后盾服务线程会不停地散发申请并异步构建 consumequeue(生产文件)和 indexFile(索引文件)。
2 数据文件
RocketMQ 的音讯数据都会写入到数据文件中,咱们称之为 commitlog。
所有的音讯都会程序写入数据文件,当文件写满了,会写入下一个文件。
如上图所示,单个文件大小默认 1G , 文件名长度为 20 位,右边补零,残余为起始偏移量,比方 00000000000000000000 代表了第一个文件,起始偏移量为 0,文件大小为 1 G = 1073741824。
当第一个文件写满了,第二个文件为 00000000001073741824,起始偏移量为 1073741824,以此类推。
从上图中,咱们能够看到音讯是一条一条写入到文件,每条音讯的格局是固定的。
这样设计有三点劣势:
程序写
磁盘的存取速度绝对内存来讲并不快,一次磁盘 IO 的耗时次要取决于:寻道工夫和盘片旋转工夫,进步磁盘 IO 性能最无效的办法就是:缩小随机 IO,减少程序 IO。
比照随机和程序读写在内存和磁盘中的体现
《The Pathologies of Big Data》这篇文章指出:内存随机读写的速度远远低于磁盘程序读写的速度。磁盘程序写入速度能够达到几百兆 /s,而随机写入速度只有几百 KB /s,相差上千倍。
疾速定位
因为音讯是一条一条写入到 commitlog 文件,写入实现后,咱们能够失去这条音讯的物理偏移量。
每条音讯的物理偏移量是惟一的,commitlog 文件名是递增的,能够依据音讯的物理偏移量通过二分查找,定位音讯位于那个文件中,并获取到音讯实体数据。
通过音讯 offsetMsgId 查问音讯数据
音讯 offsetMsgId 是由 Broker 服务端在写入音讯时生成的,该音讯蕴含两个局部:
Broker 服务端 ip + port 8 个字节;
commitlog 物理偏移量 8 个字节。
咱们能够通过音讯 offsetMsgId,定位到 Broker 的 ip 地址 + 端口,传递物理偏移量参数,即可定位该音讯实体数据。
3 生产文件
在介绍 consumequeue 文件之前,咱们先复习下音讯队列的传输模型 - 公布订阅模型,这也是 RocketMQ 以后的传输模型。
公布订阅模型具备如下特点:
生产独立:相比队列模型的匿名生产形式,公布订阅模型中生产方都会具备的身份,个别叫做订阅组(订阅关系),不同订阅组之间互相独立不会相互影响。一对多通信:基于独立身份的设计,同一个主题内的音讯能够被多个订阅组解决,每个订阅组都能够拿到全量音讯。因而公布订阅模型能够实现一对多通信。
因而,rocketmq 的文件设计必须满足公布订阅模型的需要。
那么仅仅 commitlog 文件是否能够满足需要吗?
如果有一个 consumerGroup 消费者,订阅主题 my-mac-topic,因为 commitlog 蕴含所有的音讯数据,查问该主题下的音讯数据,须要遍历数据文件 commitlog , 这样的效率是极其低下的。
进入 rocketmq 存储目录,显示见下图:
生产文件依照主题存储,每个主题下有不同的队列,图中 my-mac-topic 有 16 个队列 ; 每个队列目录下,存储 consumequeue 文件,每个 consumequeue 文件也是程序写入,数据格式见下图。
每个 consumequeue 蕴含 30 万个条目,每个条目大小是 20 个字节,每个文件的大小是 30 万 * 20 = 60 万字节,每个文件大小约 5.72M。和 commitlog 文件相似,consumequeue 文件的名称也是以偏移量来命名的,能够通过音讯的逻辑偏移量定位音讯位于哪一个文件里。
生产文件依照主题 - 队列来保留,这种形式特地适配公布订阅模型。
消费者从 broker 获取订阅音讯数据时,不必遍历整个 commitlog 文件,只须要依据逻辑偏移量从 consumequeue 文件查问音讯偏移量 , 最初通过定位到 commitlog 文件,获取真正的音讯数据。
这样就能够简化生产查问逻辑,同时因为同一主题下,消费者能够订阅不同的队列或者 tag,同时进步了零碎的可扩展性。
4 索引文件
每个音讯在业务层面的惟一标识码要设置到 keys 字段,不便未来定位音讯失落问题。服务器会为每个音讯创立索引(哈希索引),利用能够通过 topic、key 来查问这条音讯内容,以及音讯被谁生产。
因为是哈希索引,请务必保障 key 尽可能惟一,这样能够防止潜在的哈希抵触。
// 订单 Id String orderId = “1234567890”; message.setKeys(orderId);
从开源的控制台中依据主题和 key 查问音讯列表:
进入索引文件目录,如下图所以:
索引文件名 fileName 是以创立时的工夫戳命名的,固定的单个 IndexFile 文件大小约为 400 M。
IndexFile 的文件逻辑构造相似于 JDK 的 HashMap 的数组加链表构造。
HashMap 数据结构
索引文件次要由 Header、Slot Table (默认 500 万个条目)、Index Linked List(默认最多蕴含 2000 万个条目)三局部组成。
如果订单零碎发送两条音讯 A 和 B , 他们的 key 都是 “1234567890”,咱们顺次存储音讯 A , 音讯 B。
因为这两个音讯的 key 的 hash 值雷同,它们对应的哈希槽(深黄色)也会雷同,哈希槽会保留的最新的音讯 B 的索引条目序号 , 序号值是 4,也就是第二个深绿色条目。
而音讯 B 的索引条目信息的最初 4 个字节会保留上一条音讯对应的索引条目序号,索引序号值是 3 , 也就是音讯 A。
5 写到最初
Databases are specializing – the“one size fits all”approach no longer applies —— MongoDB 设计哲学
RocketMQ 存储模型设计得十分精美,笔者感觉每种设计都有其底层思考,这里总结了三点:
完满适配音讯队列公布订阅模型;数据文件,生产文件,索引文件各司其职,同时以数据文件为外围,异步构建生产文件 + 索引文件这种模式非常容易扩大到主从复制的架构;充分考虑业务的查问场景,反对音讯 key,音讯 offsetMsgId 查问音讯数据。也反对消费者通过 tag 来订阅主题下的不同音讯,晋升了消费者的灵活性。
如果我的文章对你有所帮忙,还请帮忙点赞、在看、转发一下,你的反对会激励我输入更高质量的文章,非常感谢!