目录

​一、MQ概述​

​1、MQ简介​

​2、MQ用途​

​限流削峰​

​异步解耦​

​数据收集​

​二、RocketMQ的基本概念​

​1 消息(Message)​

​2 主题(Topic)​

​3 标签(Tag)​

​4 队列(Queue)​

​5 消息标识(MessageId/Key)​

​三、系统架构​

​1 Producer​

​2 Consumer​

​3 Name Server​

​功能介绍​

​路由注册​

​路由剔除​

​路由发现​

​客户端NameServer选择策略​

​4 Broker​

​功能介绍​

​模块构成​

​集群部署​

​5 工作流程​

​具体流程​

​Topic的创建模式​

​读/写队列​


一、MQ概述

1MQ简介


        MQ, Message Queue ,是一种提供 消息队列服务 的中间件,也称为消息中间件,是一套提供了消息生产、存储、消费全过程API 的软件系统。消息即数据。一般消息的体量不会很大。

2MQ用途


从网上可以查看到很多的关于 MQ 用途的叙述,但总结起来其实就以下三点。

限流削峰


MQ 可以将系统的 超量 请求暂存其中,以便系统后期可以慢慢进行处理,从而避免了请求的丢失或系统被压垮。



RocketMQ的核心概念,一一梳理清楚_长连接


异步解耦


        上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高。         而异步调用则会解决这些问题。所以两层之间若要实现由同步到异步的转化,一般性做法就是,在这两层间添加一个MQ 层。 RocketMQ的核心概念,一一梳理清楚_数据_02



数据收集

        分布式系统会产生海量级数据流,如:业务日志、监控数据、用户行为等。针对这些数据流进行实时或批量采集汇总,然后对这些数据流进行大数据分析,这是当前互联网平台的必备技术。通过MQ完成此类数据收集是最好的选择。

二、RocketMQ的基本概念

1 消息(Message

消息是指,消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主 题。

2 主题(Topic


        Topic表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是 RocketMQ 进行消息订阅的基本单位。 topic:message 1:n message:topic 1:1         一个生产者可以同时发送多种Topic 的消息;而一个消费者只对某种特定的 Topic 感兴趣,即只可以订阅和消费一种Topic 的消息。 producer:topic 1:n consumer:topic 1:1 RocketMQ的核心概念,一一梳理清楚_RocketMQ_03





3 标签(Tag


为消息设置的标签,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ 提供的查询系统。消费者可以根据Tag 实现对不同子主题的不同消费逻辑,实现更好的扩展性。 Topic 是消息的一级分类, Tag 是消息的二级分类。 例如: ------ 生产者生产------- Topic :货物 tag= 上海 tag= 江苏 tag= 浙江 ------- 消费者消费  ----- topic= 货物 tag = 上海 topic= 货物 tag = 上海 | 浙江 topic= 货物 tag = *

4 队列(Queue


        存储消息的物理实体。一个Topic 中可以包含多个 Queue ,每个 Queue 中存放的就是该 Topic 的消息。一 个Topic 的 Queue 也被称为一个 Topic 中消息的分区(Partition)。         一个Topic 的 Queue 中的消息只能被一个消费者组中的一个消费者消费。一个 Queue 中的消息不允许同 一个消费者组中的多个消费者同时消费。 RocketMQ的核心概念,一一梳理清楚_数据_04




        在学习参考其它相关资料时,还会看到一个概念:分片(Sharding)。分片不同于分区。在RocketMQ 中,分片指的是存放相应Topic 的 Broker 。每个分片中会创建出相应数量的分区,即 Queue ,每个 Queue的大小都是相同的。 RocketMQ的核心概念,一一梳理清楚_RocketMQ_05



5 消息标识(MessageId/Key

        RocketMQ中每个消息拥有唯一的 MessageId ,且可以携带具有业务标识的 Key ,以方便对消息的查询。 不过需要注意的是,MessageId 有两个:在生产者 send() 消息时会自动生成一个 MessageId ( msgId) , 当消息到达Broker 后, Broker 也会自动生成一个 MessageId(offsetMsgId) 。 msgId 、 offsetMsgId 与 key 都 称为消息标识。 msgId :由 producer 端生成,其生成规则为:         producerIp + 进程 pid + MessageClientIDSetter 类的 ClassLoader 的 hashCode + 当前时间 + AutomicInteger 自增计数器 offsetMsgId :由 broker 端生成,其生成规则为: brokerIp + 物理分区的 offset ( Queue 中的偏移量) key :由用户指定的业务相关的唯一标识

三、系统架构

RocketMQ的核心概念,一一梳理清楚_RocketMQ_06

RocketMQ架构上主要分为四部分构成:

1 Producer

        消息生产者,负责生产消息。Producer 通过 MQ 的负载均衡模块选择相应的 Broker 集群队列进行消息投 递,投递的过程支持快速失败并且低延迟。



例如,业务系统产生的日志写入到 ​MQ

的过程,就是消息生产的过程 再如,电商平台中用户提交的秒杀请求写入到 ​MQ

的过程,就是消息生产的过程         RocketMQ中的消息生产者都是以生产者组(Producer Group)的形式出现的。生产者组是同一类生产 者的集合,这类Producer 发送相同 Topic 类型的消息。一个生产者组可以同时发送多个主题的消息。

2 Consumer


        消息消费者,负责消费消息。一个消息消费者会从Broker 服务器中获取到消息,并对消息进行相关业务处理。


例如, ​QoS

系统从 ​MQ

中读取日志,并对日志进行解析处理的过程就是消息消费的过程。 再如,电商平台的业务系统从 ​MQ

中读取到秒杀请求,并对请求进行处理的过程就是消息消费的过程。         RocketMQ中的消息消费者都是以消费者组(Consumer Group)的形式出现的。消费者组是同一类消 费者的集合,这类Consumer 消费的是同一个 Topic 类型的消息。消费者组使得在消息消费方面,实现 负载均衡 (将一个 Topic 中的不同的 Queue 平均分配给同一个 Consumer Group 的不同的 Consumer ,注意,并不是将消息负载均衡)和 容错 (一个 Consmer 挂了,该 Consumer Group 中的其它 Consumer 可以接着消费原Consumer 消费的 Queue )的目标变得非常容易。 RocketMQ的核心概念,一一梳理清楚_长连接_07




        消费者组中Consumer 的数量应该小于等于订阅 Topic 的 Queue 数量。如果超出 Queue 数量,则多出的Consumer将不能消费消息。 RocketMQ的核心概念,一一梳理清楚_RocketMQ_08




不过,一个 Topic 类型的消息可以被多个消费者组同时消费。 注意, ​1

)消费者组只能消费一个 ​Topic

的消息,不能同时消费多个 ​Topic

消息 ​2

)一个消费者组中的消费者必须订阅完全相同的 ​Topic


3 Name Server

功能介绍


        NameServer是一个 Broker 与 Topic 路由的注册中心,支持 Broker 的动态注册与发现。         RocketMQ的思想来自于 Kafka ,而 Kafka 是依赖了 Zookeeper 的。所以,在 RocketMQ 的早期版本,即在 MetaQ v1.0与 v2.0 版本中,也是依赖于 Zookeeper 的。从 MetaQ v3.0 ,即 RocketMQ 开始去掉了Zookeeper依赖,使用了自己的 NameServer 。 主要包括两个功能:         Broker管理: 接受 Broker 集群的注册信息并且保存下来作为路由信息的基本数据;提供心跳检测机制,检查Broker 是否还存活。         路由信息管理: 每个 NameServer 中都保存着 Broker 集群的整个路由信息和用于客户端查询的队列信息。Producer 和 Conumser 通过 NameServer 可以获取整个 Broker 集群的路由信息,从而进行消息的投递和消费。

路由注册


        NameServer通常也是以集群的方式部署,不过, NameServer 是无状态的,即 NameServer 集群中的各个节点间是无差异的,各节点间相互不进行信息通讯。那各节点中的数据是如何进行数据同步的呢?在Broker节点启动时,轮询 NameServer 列表,与每个 NameServer 节点建立长连接,发起注册请求。在NameServer内部维护着⼀个 Broker 列表,用来动态存储 Broker 的信息。 注意,这是与其它像 ​zk

、 ​Eureka

、 ​Nacos

等注册中心不同的地方。 这种 ​NameServer

的无状态方式,有什么优缺点: 优点: ​NameServer

集群搭建简单,扩容简单。 缺点:对于 ​Broker

,必须明确指出所有 ​NameServer

地址。否则未指出的将不会去注册。也正因为如此,​NameServer

并不能随便扩容。因为,若 ​Broker

不重新配置,新增的 ​NameServer

对于 ​Broker

来说是不可见的,其不会向这个 ​NameServer

进行注册。         Broker节点为了证明自己是活着的,为了维护与 NameServer 间的长连接,会将最新的信息以 心跳包 的方式上报给NameServer ,每 30 秒发送一次心跳。心跳包中包含 BrokerId 、 Broker 地址 (IP+Port) 、Broker名称、 Broker 所属集群名称等等。 NameServer 在接收到心跳包后,会更新心跳时间戳,记录这个Broker 的最新存活时间。

路由剔除


        由于Broker 关机、宕机或网络抖动等原因, NameServer 没有收到 Broker 的心跳, NameServer 可能会将其从Broker 列表中剔除。         NameServer中有⼀个定时任务,每隔 10 秒就会扫描⼀次 Broker 表,查看每一个 Broker 的最新心跳时间戳距离当前时间是否超过120 秒,如果超过,则会判定 Broker 失效,然后将其从 Broker 列表中剔除。 扩展:对于 ​RocketMQ

日常运维工作,例如 ​Broker

升级,需要停掉 ​Broker

的工作。 ​OP

需要怎么 做? ​OP

需要将 ​Broker

的读写权限禁掉。一旦 ​client(Consumer

或 ​Producer)

向 ​broker

发送请求,都会收到​broker

的 ​NO_PERMISSION

响应,然后 ​client

会进行对其它 ​Broker

的重试。 当​OP

观察到这个 ​Broker

没有流量后,再关闭它,实现 ​Broker

从 ​NameServer

的移除。 ​OP

:运维工程师 ​SRE

: ​Site Reliability Engineer

,现场可靠性工程师

路由发现


RocketMQ 的路由发现采用的是 Pull 模型。当 Topic 路由信息出现变化时, NameServer 不会主动推送给 客户端,而是客户端定时拉取主题最新的路由。默认客户端每30 秒会拉取一次最新的路由。 扩展: ​1

) ​Push

模型:推送模型。其实时性较好,是一个 ​

发布 ​-

订阅 ​

模型,需要维护一个长连接。而长连接的维护是需要资源成本的。该模型适合于的场景: 实时性要求较高, ​Client

数量不多, ​Server

数据变化较频繁 ​2

) ​Pull

模型:拉取模型。存在的问题是,实时性较差。 ​3

) ​Long Polling

模型:长轮询模型。其是对 ​Push

与 ​Pull

模型的整合,充分利用了这两种模型的优势,屏蔽了它们的劣势。

客户端NameServer选择策略


这里的客户端指的是 ​Producer

与 ​Consumer

客户端在配置时必须要写上 NameServer 集群的地址,那么客户端到底连接的是哪个 NameServer 节点呢?客户端首先会生产一个随机数,然后再与NameServer 节点数量取模,此时得到的就是所要连接的节点索引,然后就会进行连接。如果连接失败,则会采用round-robin 策略,逐个尝试着去连接其它节点。 首先采用的是 随机策略 进行的选择,失败后采用的是 轮询策略 。 扩展: ​Zookeeper Client

是如何选择 ​Zookeeper Server

的? 简单来说就是,经过两次 ​Shufæ e

,然后选择第一台 ​Zookeeper Server

。 详细说就是,将配置文件中的 ​zk server

地址进行第一次 ​shufæ e

,然后随机选择一个。这个选择出 的一般都是一个​hostname

。然后获取到该 ​hostname

对应的所有 ​ip

,再对这些 ​ip

进行第二次 ​shufæ e

,从 ​shufæ e

过的结果中取第一个 ​server

地址进行连接。

4 Broker

功能介绍


        Broker充当着消息中转角色,负责存储消息、转发消息。 Broker 在 RocketMQ 系统中负责接收并存储从生产者发送来的消息,同时为消费者的拉取请求作准备。Broker 同时也存储着消息相关的元数据,包括消费者组消费进度偏移offset 、主题、队列等。 ​Kafka 0.8

版本之后, ​offset

是存放在 ​Broker

中的,之前版本是存放在 ​Zookeeper

中的。

模块构成


下图为 Broker Server 的功能模块示意图。 RocketMQ的核心概念,一一梳理清楚_RocketMQ_09




Remoting Module : 整个 Broker 的实体,负责处理来自 clients 端的请求。而这个 Broker 实体则由以下模块构成。 Client Manager : 客户端管理器。负责接收、解析客户端 (Producer/Consumer) 请求,管理客户端。例如,维护Consumer 的 Topic 订阅信息 Store Service : 存储服务。提供方便简单的 API 接口,处理 消息存储到物理硬盘 和 消息查询 功能。 HA Service : 高可用服务,提供 Master Broker 和 Slave Broker 之间的数据同步功能。 Index Service : 索引服务。根据特定的 Message key ,对投递到 Broker 的消息进行索引服务,同时也提供根据Message Key 对消息进行快速查询的功能。

集群部署

RocketMQ的核心概念,一一梳理清楚_RocketMQ_10


        为了增强Broker 性能与吞吐量, Broker 一般都是以集群形式出现的。各集群节点中可能存放着相同 Topic的不同 Queue 。不过,这里有个问题,如果某 Broker 节点宕机,如何保证数据不丢失呢?其解决方案是,将每个Broker 集群节点进行横向扩展,即将 Broker 节点再建为一个 HA 集群,解决单点问题。         Broker节点集群是一个主从集群,即集群中具有 Master 与 Slave 两种角色。 Master 负责处理读写操作请求,Slave 负责对 Master 中的数据进行备份。当 Master 挂掉了, Slave 则会自动切换为 Master 去工作。所以这个Broker 集群是主备集群。一个 Master 可以包含多个 Slave ,但一个 Slave 只能隶属于一个 Master 。Master与 Slave 的对应关系是通过指定相同的 BrokerName 、不同的 BrokerId 来确定的。 BrokerId 为 0 表示Master ,非 0 表示 Slave 。每个 Broker 与 NameServer 集群中的所有节点建立长连接,定时注册 Topic 信息到所有NameServer 。




5 工作流程

具体流程


1 )启动 NameServer , NameServer 启动后开始监听端口,等待 Broker 、 Producer 、 Consumer 连接。 2 )启动 Broker 时, Broker 会与所有的 NameServer 建立并保持长连接,然后每 30 秒向 NameServer 定时发送心跳包。 3 )发送消息前,可以先创建 Topic ,创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上,当然,在创建Topic 时也会将 Topic 与 Broker 的关系写入到 NameServer 中。不过,这步是可选的,也可以在发送消息时自动创建Topic 。 4 ) Producer 发送消息,启动时先跟 NameServer 集群中的其中一台建立长连接,并从 NameServer 中获取路由信息,即当前发送的Topic 消息的 Queue 与 Broker 的地址(IP+Port)的映射关系。然后根据算法策略从队选择一个Queue ,与队列所在的 Broker 建立长连接从而向 Broker 发消息。当然,在获取到路由信息后,Producer 会首先将路由信息缓存到本地,再每 30 秒从 NameServer 更新一次路由信息。 5 ) Consumer 跟 Producer 类似,跟其中一台 NameServer 建立长连接,获取其所订阅 Topic 的路由信息,然后根据算法策略从路由信息中获取到其所要消费的Queue ,然后直接跟 Broker 建立长连接,开始消费其中的消息。Consumer 在获取到路由信息后,同样也会每 30 秒从 NameServer 更新一次路由信息。不过不同于Producer 的是, Consumer 还会向 Broker 发送心跳,以确保 Broker 的存活状态。

Topic的创建模式


手动创建 Topic 时,有两种模式:         集群模式:该模式下创建的Topic 在该集群中,所有 Broker 中的 Queue 数量是相同的。         Broker模式:该模式下创建的 Topic 在该集群中,每个 Broker 中的 Queue 数量可以不同。 自动创建 Topic 时,默认采用的是 Broker 模式,会为每个 Broker 默认创建 4 个 Queue 。

/写队列


        从物理上来讲,读/ 写队列是同一个队列。所以,不存在读 / 写队列数据同步问题。读 / 写队列是逻辑上进行区分的概念。一般情况下,读/ 写队列数量是相同的。         例如,创建Topic 时设置的写队列数量为 8 ,读队列数量为 4 ,此时系统会创建 8 个 Queue ,分别是 0 1 2 3 4 5 6 7。 Producer 会将消息写入到这 8 个队列,但 Consumer 只会消费 0 1 2 3 这 4 个队列中的消息, 4 5 6 7中的消息是不会被消费到的。         再如,创建Topic 时设置的写队列数量为 4 ,读队列数量为 8 ,此时系统会创建 8 个 Queue ,分别是 0 1 2 3 4 5 6 7。 Producer 会将消息写入到 0 1 2 3 这 4 个队列,但 Consumer 只会消费 0 1 2 3 4 5 6 7 这 8 个队列中 的消息,但是4 5 6 7 中是没有消息的。此时假设 Consumer Group 中包含两个 Consuer , Consumer1 消 费0 1 2 3 ,而 Consumer2 消费 4 5 6 7 。但实际情况是, Consumer2 是没有消息可消费的。         也就是说,当读/ 写队列数量设置不同时,总是有问题的。那么,为什么要这样设计呢?         其这样设计的目的是为了,方便Topic 的 Queue 的缩容。         例如,原来创建的Topic 中包含 16 个 Queue ,如何能够使其 Queue 缩容为 8 个,还不会丢失消息?可以动态修改写队列数量为8 ,读队列数量不变。此时新的消息只能写入到前 8 个队列,而消费都消费的却是16个队列中的数据。当发现后 8 个 Queue 中的消息消费完毕后,就可以再将读队列数量动态设置为 8 。整个缩容过程,没有丢失任何消息。         perm用于设置对当前创建 Topic 的操作权限: 2 表示只写, 4 表示只读, 6 表示读写。