目录
一、MQ概述
源码地址:https://github.com/apache/rocketmq
中文文档:https://github.com/apache/rocketmq/tree/master/docs/cn
商业版:https://www.aliyun.com/product/rocketmq
官网翻译:http://www.itmuch.com/books/rocketmq/
FAQ:http://rocketmq.apache.org/docs/faq/
RocketMQ常用管理命令
https://blog.csdn.net/gwd1154978352/article/details/80829534
RocketMQ默认配置
https://www.cnblogs.com/jice/p/11981107.html
RocketMQ架构
http://rocketmq.apache.org/docs/rmq-arc/
1、MQ简介
MQ,
Message Queue
,是一种提供
消息队列服务
的中间件,也称为消息中间件,是一套提供了消息生产、存储、消费全过程API
的软件系统。消息即数据。一般消息的体量不会很大。
2、MQ用途
从网上可以查看到很多的关于
MQ
用途的叙述,但总结起来其实就以下三点。
限流削峰
MQ
可以将系统的
超量
请求暂存其中,以便系统后期可以慢慢进行处理,从而避免了请求的丢失或系统被压垮。
异步解耦
上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高。
而异步调用则会解决这些问题。所以两层之间若要实现由同步到异步的转化,一般性做法就是,在这两层间添加一个MQ
层。
数据收集
分布式系统会产生海量级数据流,如:业务日志、监控数据、用户行为等。针对这些数据流进行实时或批量采集汇总,然后对这些数据流进行大数据分析,这是当前互联网平台的必备技术。通过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
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
中的消息不允许同 一个消费者组中的多个消费者同时消费。
在学习参考其它相关资料时,还会看到一个概念:分片(Sharding)。分片不同于分区。在RocketMQ 中,分片指的是存放相应Topic
的
Broker
。每个分片中会创建出相应数量的分区,即
Queue
,每个 Queue的大小都是相同的。
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架构上主要分为四部分构成:
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
)的目标变得非常容易。
消费者组中Consumer
的数量应该小于等于订阅
Topic
的
Queue
数量。如果超出
Queue
数量,则多出的Consumer将不能消费消息。
不过,一个
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
的功能模块示意图。
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
对消息进行快速查询的功能。
集群部署
为了增强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
表示读写。