前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >深度解析RocketMQ的背后原理

深度解析RocketMQ的背后原理

原创
作者头像
写bug的高哈哈
发布2025-01-04 21:53:12
发布2025-01-04 21:53:12
940
举报

在分布式系统中,通信是关键环节,而消息队列在此扮演着至关重要的角色,尤其是在处理异步通信和削峰填谷方面。随着业务量的增长和请求量的激增,消息队列逐渐成为设计复杂系统时的标准配置。RocketMQ 作为消息队列中的佼佼者,其地位堪比数据库领域的 MySQL,成为技术选型的首选。无论是架构师还是技术求职者,理解其核心原理都是十分必要的。

本文将深入探讨 RocketMQ 的内在机制。首先,我们将从主题模型开始,理解 RocketMQ 的逻辑架构;接着,我们将了解其物理架构,揭示逻辑结构的运作方式;最后,我们将从微观层面分析其存储结构。

RocketMQ 主题模型

在分布式应用中,消息传递的效率和可靠性至关重要,而消息队列技术正是解决这些问题的关键工具。RocketMQ 作为一种高效的消息队列系统,其主题模型是理解其工作原理的基础。

首先,让我们深入了解 RocketMQ 的主题模型。在这一模型中,消息的发送者被称为发布者,而接收者则被称为订阅者。消息本身被存储在一个被称为主题的容器中。发布者负责将消息发布到主题,而订阅者则需要事先订阅该主题,以便能够接收到消息。

以一个典型的电子商务系统为例,我们可以设想系统中存在订单的生成者和消费者。在这种情况下,订单生成者相当于发布者,而订单消费者则相当于订阅者。订单 ID 以数字形式表示,并且是递增的。发布者集群由多台服务器组成,每台服务器都能够向主题发送订单消息。

在主题的内部结构中,我们可以看到它由多个队列构成。发布者生成的每条订单消息都会被发送到主题中的一个特定队列。例如,根据订单 ID 的奇偶性,发布者可能会选择将消息发送到不同的队列。然而,这只是一种策略,实际上还有许多其他的分发策略,如随机分发或轮询等。

在订阅者集群的一侧,我们可以看到多台机器共同消费一个主题的消息。但是,需要注意的是,每个队列只能被一个订阅者服务器消费。例如,订阅者服务器 1 可能负责消费队列 1,而订阅者服务器 2 则消费队列 2。如果订阅者服务器 1 或 2 出现故障,其他服务器将接管消费任务。因此,为了保证系统的稳定性,通常建议订阅者集群中的服务器数量至少与主题中的队列数量相等。所以通常来说要保证订阅者集群中的服务器个数大于等于主题中队列个数。

如果订阅者集群只包含一台机器,那么这台机器将同时消费队列 1 和队列 2。虽然一个队列只能绑定一个订阅者服务器,但一个订阅者服务器可以关联多个队列,这样可以提高资源的利用率。

此外,我们还注意到,即使订阅者集群已经消费到了某个位置,消费完的数据并不会被删除。这是因为可能存在多个订阅者集群,它们可能还没有完成消费。因此,每个订阅者集群都会维护自己的消费位置,并且在消费完队列后,会将消费位置向右移动一位。

最后,关于为什么要将主题分为多个队列,而不是使用单个队列,原因在于提高资源的利用率。如果只使用一个队列,那么只有一台订阅者服务器能够工作,其他服务器则无法参与消费,这将导致资源的浪费。通过队列的拆分,可以使得更多的服务器参与到消息的消费中,从而提高整个系统的效率。

讲到这里,我想到 RocketMQ 一个很经典的面试题,那就是,RocketMQ 是如何保证顺序性的?我们已经知道 RocketMQ 内部有很多的队列,如果我们把需要保证顺序的消息发送到其中的某一个队列上,就可以利用队列来保证有序性。

在实际操作中,为了确保消息的顺序性,可以采用特定的策略,例如根据消息 ID 的哈希值来决定其发送到哪个队列。这样,具有相同哈希值的消息将被发送到同一个队列中,从而保证了消息的顺序性。

RocketMQ 架构

RocketMQ 的物理架构由几个关键组件构成:生产者(Producer)、消费者(Consumer)、命名服务器(NameServer)和代理服务器(Broker)。生产者负责创建消息,而消费者则负责接收和处理这些消息,这两个角色在消息传递过程中扮演着基础的角色。

Broker 是核心,它对外提供服务,生产者将消息生产至 Broker,消费者从 Broker 消费消息。Broker 与主题的关系是,一个主题包含多个队列,一个主题可以分布在多个 Broker 上,一个 Broker 也可以配置多个主题,形成多对多关系。Broker 作为一个单机进程,受限于存储容量,不可能存放过多消息。如果主题消息量足够大,可能需要分布在多台 Broker 机器上。

如何管理众多 Broker?NameServer 的作用类似于 ZooKeeper,NameServer 作为一个注册中心。Broker 将自己的信息注册至 NameServer,使 NameServer 拥有 Broker 的路由信息。生产者和消费者通过 NameServer 找到所需的 Broker 进行通信,它们定期从 NameServer 获取路由信息,类似于 Dubbo 的服务发现过程。

在生产环境中,Broker 采用集群加主从部署方式。如果主 Broker 故障,从 Broker 将提供服务,但此时仅提供消费服务,不允许写入。

为保证高可用性,NameServer 也采用集群部署。与 ZooKeeper 不同,NameServer 没有 Leader,每个 NameServer 与所有 Broker 建立连接,Broker 定时向所有 NameServer 发送心跳包。

RocketMQ 存储

RocketMQ 的存储实现核心文件为 CommitLog 和 ConsumeQueue。

CommitLog 是存储主体,文件大小默认为 1GB,文件名长度为 20 位,起始偏移量左边补零。例如,00000000000000000000 代表第一个文件,起始偏移量为 0,大小为 1GB。当文件写满后,写入下一个文件,如 00000000001073741824,起始偏移量为 1073741824。消息顺序写入日志文件,当文件写满后,写入下一个文件。

在这里补充一句,左边补零是个非常有意思的操作,我们知道字符类型的数据天然是字典排序的,这种情况下会出现字符串"10"小于字符串"9"的情况,如果我们对字符串"9"左边补一个 0,就会出现"09"小于"10"了,通过这种操作让字符串拥有了数学特性,如果你用过 Hbase,可以通过这个方法实现类似 Redis ZSet 的效果。

若需检索 CommitLog 中的特定消息,直接遍历非常慢。RocketMQ 使用 ConsumeQueue 作为索引文件,保存消息在 CommitLog 中的起始偏移量、大小和哈希等信息。ConsumeQueue 文件内容采用固定长度设计,每条信息 20 字节,包括 8 字节的 CommitLog 物理偏移量、4 字节的消息大小和 8 字节哈希。单个文件由 30 万个条目组成,可像数组一样随机访问每个条目。

在一个 Broker 实例下所有的队列共用一个日志数据文件来存储消息。而很多中间件会采用不同的队列写入不同的文件,这样看起来条理更清晰。但 RocketMQ 为什么要一意孤行呢?原因是提高数据的写入效率 ,不分主题,我们就会有更大的几率批量写入,但同时也意味着读取消息的时候需要遍历整个大文件,这是非常耗时的。

所以,RocketMQ 使用 ConsumeQueue 作为索引文件,能够提升读取效率。我们可以直接根据消息序号,计算出索引的全局位置,然后直接读取这条索引,找到消息。

总结

今天我们一起学习了 RocketMQ 原理。通过学习 RocketMQ,我们知道了 RocketMQ 是基于主题模型的,而主题是通过多个队列来实现的。知道了 Broker 是对外提供服务的,以及 Broker 和主题的关系,而 NameServer 则是 Broker 的大管家。最后介绍了 RocketMQ 的存储机制,其实主要就是依靠 2 个文件,CommitLog 和 ConsumeQueue。CommitLog 存储的是数据,ConsumeQueue 是索引。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • RocketMQ 主题模型
  • RocketMQ 架构
  • RocketMQ 存储
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档