1.RocketMQ简介
RocketMQ是阿里巴巴2016年发布的MQ中间件,使用Java语言开发,RocketMQ是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可用的消息发布与订阅服务
RocketMQ官网给出的RocketMQ的核心特性如下:
2.什么是MQ
2.1 定义
刚才介绍RocketMQ的时候我们说到了它是一个MQ中间件,那么MQ中间件又是什么呢,它是一个统称,全名Message Queue也就是消息队列,简单来说就是一个放置消息的队列,可以向里发送数据,也可以读取里边的数据,我们把向里发送数据的一方叫生产者读取数据的一方叫消费者
MQ相当于一个名词,如今市面上比较流行的MQ主要有RabbitMQ、RocketMQ、Kafka,RocketMQ只是MQ其中的一种
2.2 特点
既然MQ是消息队列的统称,那消息队列主要有什么作用呢,MQ可以总结出三大主要作用
- 异步
- 解耦合
- 削峰限流
2.2.1 异步
我们知道同步处理是指一个请求的所有逻辑必须依次执行并完成,整个过程结束后才返回结果,那异步就指请求发出后立即返回响应,具体的处理逻辑在后台逐步执行,无需调用方等待。
比如用户在电商网站下单后,需要发送短信通知。如果用同步方式,系统会等短信发完再返回结果,速度慢且容易出问题。异步方式下,下单后只需把“发短信”消息放入 MQ,立即返回结果,短信服务再异步发送短信。
2.2.2 解耦合
解耦合可以理解为降低两个系统或模块之间的依赖关系,使它们能够相对独立地运作,从而提升系统的灵活性和可维护性。
比如,订单系统下单后,既要通知库存系统扣减库存,还要通知积分系统增加积分。如果用传统的“直接调用”方式,订单系统需要集成库存和积分的接口,当其中一个系统升级或出现故障时,订单系统也会受影响。有了 MQ 后:订单系统只负责把“下单消息”放进 MQ。库存服务、积分服务分别监听 MQ,收到消息后各自处理。这样,服务之间不直接依赖,便于独立开发和演进
2.2.3 削峰限流
削峰限流就是当系统突然遇到大量访问时,不一下子全接进来,而是让这些请求排队慢慢处理,防止系统被瞬间压垮。
比如,秒杀活动时,用户瞬间大量下单。如果全部请求直接打到数据库,数据库很快就会“爆掉”,有了 MQ 后:用户请求先写入 MQ,MQ 充当“缓冲区”。下游系统(比如数据库)按照自己的处理能力,从 MQ 中逐条拉取消息,逐步处理。即使瞬时请求量很大,也不会把系统压垮。
3.RocketMQ核心概念
Producer:消息的发送者,负责发送消息到RocketMQ
ProducerGroup:生产者组,可以把多个生产者归为一组
Consumer:消息者从RocketMQ拉取Producer发送过来的消息
ConsumerGroup:消息者组,把多个消息者归为一组
Broker:存储消息和传输消息的通道,负责保存Producer发来的消息,等待Consumer拉取,也可以理解为一个RockerMQ节点就是一个Broker
Topic:即主题,是消息的逻辑分类。Producer 将消息发送到指定的 Topic,Broker 则按照 Topic 对消息进行隔离和管理。每个 Topic 下可以包含多个不同的消息队列,实现消息的分类存储与消费。
Queue:一个Topic中可以有多个Queue,发送到Topic中的消息分布在Queue上
NameServer:类似注册中心,管理所有Broker的地址信息,帮Producer和Consumer找到对应的Broker
4.RocketMQ架构图
上面我们把RocketMQ的核心概念都介绍了一下,下面我们用一张图梳理一下它们之间的关系
我们先简单介绍一下 RocketMQ 的架构图。在整个 RocketMQ 系统中,NameServer 相当于“控制中心”,负责路由管理。它记录了各个 Broker 的信息,生产者(Producer)在发送消息时,会通过 NameServer 获取 Topic 所对应的 Broker 地址,从而将消息发送到指定的 Broker。消费者(Consumer)同样通过 NameServer 获取需要消费的消息所在 Broker 和 Queue 的信息,完成消息的拉取和消费。
也就是说,在 RocketMQ 集群模式下,生产组中的某个 Producer 向指定 Topic 写入消息时,NameServer 会根据路由信息决定具体将消息写入到哪个 Broker。消费者则通过 NameServer 获取消息所在的 Broker 地址和队列(Queue),实现对消息的消费。
每个 Topic 下包含若干个队列(Queue),消息最终是存储在这些队列中的,消费者消费的实际上也是 Queue 里的消息。
如果是单体架构(单节点模式),只有一个 Broker,此时所有消息都集中存储在该 Broker 下。Producer 默认将消息发送到这个 Broker,Consumer 也只从这个 Broker 中拉取消息。
5.RocketMQ安装
5.1 下载RocketMQ
从RocketMQ官网下载的linux版的安装包,官网:https://rocketmq.apache.org/dowloading/releases/
我们下载的版本为4.9.2
5.2 上传到服务器
将刚才下载好的rocketmq-all-4.9.2-bin-release.zip上传到我们的服务器中
5.3 解压安装包
执行如下命令
unzip rocketmq-all-4.9.2-bin-release.zip
RocketMQ解压后的文件目录如下
目录说明
- Benchmark:包含一些性能测试的脚本
- Bin:可执行文件目录
- Conf:配置文件目录
- Lib:第三方依赖
- LICENSE:授权信息
- NOTICE:版本公告
5.4 配置环境变量
修改全局配置
vim /etc/profile
在文件末尾添加(ip地址为你的服务器ip地址)
export NAMESRV_ADDR=192.168.116.129:9876
5.5 修改nameServer的运行脚本
进入bin目录下,修改runserver.sh文件,将71行和76行的Xms和Xmx等改小一点,因为如果不修改可能我们服务器的内存不够用
vim runserver.sh
5.6 修改broker的运行脚本
进入bin目录下,修改runbroker.sh文件,修改67行
vim runbroker.sh
5.7 修改broker的配置文件
进入conf目录下,修改broker.conf文件
vim broker.conf
添加如下内容
namesrvAddr=localhost:9876
autoCreateTopicEnable=true
brokerIP1=192.168.116.129
添加参数解释
- namesrvAddr:由于Name Server和Broker部署在同一服务器上,这里将
namesrvAddr
设置为localhost是合适的。这确保了Broker能够直接与本地运行的Name Server进行通信。 - autoCreateTopicEnable:启用自动创建主题功能,这样当尝试向一个尚未存在的主题发送消息时,系统会自动创建该主题。如果不启用此功能,则需要提前手动创建所有必要的主题。
- brokerIP1:为了确保从外部网络也能访问到Broker,这里需要为Broker配置一个公网IP地址。若不指定公网IP,仅使用默认的内网地址(例如在阿里云环境下的内网IP),则可能导致本地开发环境无法连接到Broker进行测试或调试工作。不能设置为localhost
5.8 启动
首先在安装目录下创建一个logs文件夹,用于存放日志
mkdir logs
一次运行两条命令
启动nameSrv,nohup让程序在后台运行
nohup sh bin/mqnamesrv > ./logs/namesrv.log &
启动broker 这里的-c是指定使用的配置文件
nohup sh bin/mqbroker -c conf/broker.conf > ./logs/broker.log &
查看启动结果
5.9 RocketMQ控制台RocketMQ-Console
5.9.1 介绍
RocketMQ-Console 是一个用于管理和监控 Apache RocketMQ 消息队列系统的可视化网页工具。你可以把它理解成 RocketMQ 的“管理后台”或“控制面板”。
RocketMQ-Console 主要有以下几个功能:
- 查看消息队列的运行状态
比如哪些 Topic 在运行,每个 Topic 下有多少消息,消费情况如何等。 - 管理 Topic 和 Consumer Group
可以新建、删除或者查询 Topic(消息主题)和 Consumer Group(消费组)。 - 查看消息轨迹
能看到消息的生产、消费、发送时间等详细信息,有助于排查消息丢失或延迟的问题。 - 监控消费情况
直观地看到消息有没有被消费、消费是否有积压等。 - 运维管理
支持 Broker 节点的管理、集群健康状态检查等基础运维功能。
5.9.2 下载安装
在RocketMQ官网下载最下方下载
下载后解压出来,在跟目录下执行
mvn clean package -Dmaven.test.skip=true
在文件下target目录下有打包完的jar文件
将jar包上传到服务器上
5.9.3启动
执行如下命令
nohup java -jar ./rocketmq-dashboard-1.0.0.jar rocketmq.config.namesrvAddr=127.0.0.1:9876 > ./rocketmq-4.9.2/logs/dashboard.log &
命令拓展:–server.port指定运行的端口
–rocketmq.config.namesrvAddr=127.0.0.1:9876 指定namesrv地址
访问: http://192.168.116.129:8080
6.RocketMQ快速入门
前面通过RocketMQ的架构图已经了解过,消息发送者通过Nameserver把消息发送到指定的Broker上,消费者通过Nameserver读取到所监听的消息,下面讲解下如何通过代码去实现消息生产者发送消息和消费消费者读取消息
6.1 消息发送和监听的流程
先梳理一下消费发送和监听的流程,然后在去通过代码实现
6.1.1 消息生产者
1.创建消息生产者(Producer)并指定生产者组名
首先,需要实例化一个消息生产者对象,并为其指定一个唯一的“生产者组名”(Producer Group),用于同一业务场景下的消息发送。
2.设置 NameServer 地址
配置生产者实例所连接的 RocketMQ NameServer 地址。NameServer 充当服务发现的角色,帮助 Producer 查找消息队列的 Broker。
3.启动 Producer 实例
在发送消息前,需调用 start()
方法启动 Producer,完成相关资源的初始化工作。
4.创建消息对象
构造消息对象(Message
),并指定消息的主题(Topic)、标签(Tag)、消息体(Body)等属性。
- Topic:用于标识消息所属的业务类别。
- Tag:对同一 Topic 下的消息进一步分类。
- Body:消息的具体内容,一般为字节数组。
5.发送消息
调用 Producer 的 send()
方法,将消息发送到指定的 Topic。发送方式可选择同步、异步或单向。
6.关闭 Producer 实例
发送完成后,调用 shutdown()
方法关闭 Producer 以释放资源。
6.1.2 消息消费者
1.创建消费者(Consumer)并指定消费者组名
实例化一个消费者对象,并为其指定一个唯一的“消费者组名”(Consumer Group),用于同一业务场景下的消息消费。
2.设置 NameServer 地址
配置消费者实例所连接的 RocketMQ NameServer 地址,便于消费者发现 Broker 和订阅的 Topic。
3.订阅消息:指定 Topic 和 Tag
通过订阅(subscribe)方法,指定要消费的消息主题(Topic)和标签(Tag),支持对消息进行筛选。
4.注册消息监听器并处理消息
实现消息监听接口(MessageListener),注册消息处理逻辑,消费到消息后执行具体的业务处理。
5.启动消费者实例
调用 start()
方法启动消费者,开始监听消息并进行消费。
6.2 代码实现
创建一个maven项目
6.2.1 加入依赖
在pom文件中加入如下的依赖
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
6.2.2 编写消费者
先启动消费者,注意要挂起当前的jvm,因为如果不挂起 JVM,@Test结束后主线程直接退出,RocketMQ 消费者线程也会被关闭,无法持续消费消息。用 System.in.read() 可以简单地让 JVM 挂起,方便你观察消息消费
NamesrvAddr地址设置为刚才咋子服务器上启动的Namesrv地址
@Test
public void simpleConsumer() throws Exception {
// 创建一个消费者,simpleConsumer为消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("simpleConsumer");
// 连接namesrv
consumer.setNamesrvAddr("192.168.116.129:9876");
// 订阅一个主题 * 标识订阅这个主题中所有的消息 后期会有消息过滤
consumer.subscribe("testTopic", "*");
// 设置一个监听器(一直监听的,异步回调方式)
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
// 这个就是消费的方法(业务处理)
System.out.println("我是消费者");
System.out.println(msgs.get(0).getBody());
System.out.println("消息内容:" + new String(msgs.get(0).getBody()));
System.out.println("消费上下文:" + context);
// 返回值 CONSUME_SUCCESS成功,消息会从mq出队
// RECONSUME_LATER(报错/null) 失败 消息会重新回到队列 过一会重新投递出来 给当前消费者或其他消费者消费的
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动
consumer.start();
// 挂起当前的jvm
System.in.read();
}
6.2.3 编写生产者
向topic中发送消息
@Test
public void simpleProducer() throws Exception {
// 创建一个生产者,simpleProducer为生产者组名
DefaultMQProducer producer = new DefaultMQProducer("simpleProducer");
// 连接namesrc
producer.setNamesrvAddr("192.168.116.129:9876");
// 启动
producer.start();
// 创建一个消息
Message message = new Message("testTopic", "第一次发送消息".getBytes());
// 发送消息
SendResult sendResult = producer.send(message);
System.out.println(sendResult.getSendStatus());
// 关闭生产者
producer.shutdown();
}
6.2.3 观察结果
回到消费者的控制台发现消费者已经读取到消息了
6.3 生产者组和消费者组
6.3.1 生产者组
在 RocketMQ 中,生产者组主要用于将同一业务下的多个生产者实例进行分组,实现集群管理和业务隔离,通常没有额外的业务意义。
topic默认会有4个queue队列,生产者向topic中发送消息时,消息会均匀的发到queue中,下面向topic中循环发送10条消息
@Test
public void simpleProducer() throws Exception {
// 创建一个生产者 (并指定一个组名)
DefaultMQProducer producer = new DefaultMQProducer("simpleProducer");
// 连接namesrc
producer.setNamesrvAddr("192.168.116.129:9876");
// 启动
producer.start();
// 创建一个消息
for (int i = 0; i < 10; i++) {
Message message = new Message("testTopic", "第一次发送消息".getBytes());
// 发送消息
SendResult sendResult = producer.send(message);
System.out.println(sendResult.getSendStatus());
}
// 关闭生产者
producer.shutdown();
}
可以看到消息均匀的分布在4个队列中
6.3.2 消费者组
正常情况下,生产者将消息发送到 Broker 后,Broker 会针对每个订阅该 Topic 的消费者组分别投递一份相同的消息。也就是说,消息在 Broker 端是以消费者组为单位进行分发的。例如,如果有 3 个不同的消费者组都订阅了同一个 Topic,当生产者向该 Topic 发送一条消息时,这条消息会分别投递给这 3 个消费者组,每个组都能各自消费到这条消息。
同一个消费者组可以同时订阅多个 Topic,但组内的所有消费者实例订阅的 Topic 必须保持一致,也就是说,一个消费者组内的消费者们需要监听相同的 Topic 集合。
当消费者组接收到消息后,消息会在组内的各个消费者实例之间进行分配,分配方式有负载均衡模式和广播模式两种。负载均衡模式下,消息会被均匀分配给组内的各个消费者实例,每个实例都有机会消费到部分消息;广播模式下,每条消息会被推送给组内的所有消费者实例,每个实例都会收到同样的消息。
6.3.3 举例
假设现在有一个 Topic,包含 4 个队列(Queue),一个消费者组(Consumer Group),以及一个生产者组(Producer Group)。生产者向该 Topic 发送 4 条消息,这些消息会被均匀地分布到 4 个队列中。
当消费者组中只有一个消费者实例时,这个消费者会同时消费这 4 个队列中的所有消息。
当消费者组中新增一个消费者实例,RocketMQ 会自动进行负载均衡(Rebalance),将 Topic 下的 4 个队列平均分配给两个消费者。此时,每个消费者会监听并消费 2 个队列中的消息。注意:每个队列只能被分配给一个消费者实例,但一个消费者实例可以同时消费多个队列。
如果再新增一个消费者实例,RocketMQ 会再次进行负载均衡,将 4 个队列分配给 3 个消费者。分配方式通常是:2 个消费者各分配 1 个队列,另 1 个消费者分配 2 个队列(具体分配由 RocketMQ 的算法决定)。
继续新增消费者实例,总数达到 4 个时,RocketMQ 会将每个队列分别分配给 4 个消费者,每个消费者各自监听 1 个队列。
如果消费者组中消费者实例的数量超过了队列数(比如有 5 个消费者,但只有 4 个队列),则会有多余的消费者实例无法分配到队列,这些消费者就不会消费到任何消息。因此,建议消费者实例的数量不要超过队列数量。
总结:RocketMQ 消费者组内的消费者实例会通过负载均衡机制均匀分配 Topic 下的队列。每个队列只能被一个消费者实例消费,但一个消费者实例可以消费多个队列。当消费者数量大于队列数量时,多余的消费者将无法分配到队列,也就不会消费到消息。因此,最佳实践是确保消费者实例数不大于队列数,以充分利用资源。
6.4 代理者位点和消费者位点
代理点位指的是 Broker(代理服务器)为每个消息队列(MessageQueue)分配的当前最新的一条消息的 Offset。如果一个消息队列的代理者位点为 100,说明这个队列里有 100 个消息,最新消息的 offset 是 99(offset 通常从 0 开始)。
消费者位点指的是 消费者组对某个消息队列消费到的位置,即已经成功消费的最大 Offset 值。某消费者组对某个队列的消费位点是 80,说明这个组已经消费了 offset=0 到 offset=79 的消息,下次会从 offset=80 开始拉取。
这里要注意,topic会为不同的消费者组维护独立的消费者位点,也就是说一个topic中的数据被一个消费者组消费了不影响其他消费者组继续消费
通过RocketMQ仪表板就能看出来消费者组对某个topic的消费情况