RocketMq源码核心篇整体栏目
| 内容 | 链接地址 |
|---|---|
| 【一】环境搭建、基本使用、可视化界面 | https://zhenghuisheng.blog.csdn.net/article/details/147481401 |
| 【二】rocketmq集群搭建(docker版-2主2从) | https://zhenghuisheng.blog.csdn.net/article/details/154921615 |
| 【三】dashboard安装搭建和启动详解 | https://zhenghuisheng.blog.csdn.net/article/details/155371854 |
| 【四】rocketmq的普通消息详解 | https://zhenghuisheng.blog.csdn.net/article/details/155679428 |
如需转载,请附上链接: https://blog.csdn.net/zhenghuishengq/article/details/155679428
rocketmq的普通消息详解
一、Rocketmq的普通消息详解
前面文章已经完成了rocketmq的集群搭建和dashboard的可视化面板搭建,接下来这篇文章正式进入RocketMq的基本使用,本篇文章直接进入到Rocketmq的普通消息的详解。 本文基于 RocketMQ4.9.4(Dashboard 1.x),某些Dashboard 展示行为可能与 RocketMQ 5.x 不一致 。
在使用前,可以先看一下官网对普通消息的定义和使用场景--官网普通消息,先借助于官网对普通消息的定义:
- 普通消息一般应用于微服务解耦、事件驱动、数据集成等场景,这些场景大多数要求数据传输通道具有可靠传输的能力,且对消息的处理时机、处理顺序没有特别要求
上面两段皆来自于官网对普通消息的描述,接下来对于开发者的我们,需要从更多不同的角度学习rocketmq的普通消息,了解其业务场景和使用场景
1,rocketmq和dashboard启动
环境咱们已经搭建好了,接下来就对这个可视化界面做一个详细的了解,首先需要启动rocketmq和dashboard,所以这个系列文章都优质密切的关系,因此需要详细的了解前面的文章。
首先启动rocketmq集群,直接通过compose启动,因此切换到 /usr/local/env/rocketmq/cluster 目录下, 然后启动rocketmq集群,以后台的方式启动
shell
cd /usr/local/env/rocketmq/cluster
sudo docker compose up -d

随后切换到 /usr/local/env/rocketmq/cluster/bashboard 路径下,随后直接启动脚本即可,在上一篇文章中有配置
shell
cd /usr/local/env/rocketmq/cluster/bashboard
sudo bash start-dashboard.sh
会在控制台直接出现启动成功的日志
shell
检查是否有进程占用端口 8888 ...
检查是否已有 dashboard.pid ...
启动 RocketMQ Dashboard ...
启动成功!PID=6695
日志文件:/usr/local/env/rocketmq/cluster/bashboard/dashboard.log
随后浏览器打开可视化页面 **http://192.168.1.2:8888/#/**, 那么就代表着项目启动成功

在后续文中,就不会再对这个启动进行阐述。
2,生产消息
在完成rocketmq的启动和dashboard的启动之后,接下来就正式的讲解普通消息的基本使用,学习正常的发送消息和消费消息,在学习前,也可以再官网中看以下普通消息的生命周期流程,简单来说就是一下流程 生产者生产消息-->消息发送到服务端待消费-->消息被消费者发现然后消费消息,并提交消费结果-->消费结构提交,修改offset偏移量-->消息被覆盖删除

接下来通过原生代码对rocketmq的普通消息进行生产和提交,首先需要加入以下依赖
yml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.4</version>
</dependency>
2.1,生产者写入消息
随后创建一个 DefaultMQProducer 默认的生产者组对象,然后必须绑定nameserver地址,随后启动producer,再执行send发送10万条消息,每条消息在36*2=72字节,就是每条消息大概在0.07kb左右,包括内部的一些其他参数,每条消息大概在120-200个字节,所以10万条大概就是15m的数据量
java
package com.zhs.study.source;
import cn.hutool.core.lang.UUID;
import com.google.common.collect.Lists;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @Author zhenghuisheng
* @Date:2025/12/07 16:28
*/
public class ProducerDemo {
public static void main(String[] args) throws MQBrokerException, RemotingException, InterruptedException, MQClientException {
// 1. 创建生产者(Producer Group 名称)
DefaultMQProducer producer = new DefaultMQProducer("zhsProducerGroup");
// 所有参数中,该参数必须绑定
producer.setNamesrvAddr("192.168.1.2:9876");
// 2. 启动 Producer(不启动会报错)
producer.start();
System.out.println("Producer 启动完成");
// 4. 发送消息
System.out.println("开始发送消息...");
// 3. 构建消息对象
List<Message> list = new ArrayList<Message>();
for (int i = 0; i < 100000; i++) {
String uuid = UUID.randomUUID().toString();
uuid = uuid + uuid;
Message msg = new Message("zhsTopicV1", "TagA", uuid.getBytes());
list.add(msg);
}
List<List<Message>> partition = Lists.partition(list, 1000);
for (List<Message> msg : partition) {
producer.send(msg);
System.out.println("消息批量发送完成");
}
// 5. 关闭 Producer
producer.shutdown();
}
}
需要注意的点是,官方明确批量拉取的数据量不能大于1m,这里涉及到batch缓冲区的大小,超过1m就会报错MessageIllegalException
执行完成之后,再看dashboard,可以发现在BrokerA集群中有10万条数据,并且master和slave都有存,每个master的broker大概5w条数据,但是这个并不准,因为我们没有干预这个topic,因此用官方的原话说就是:topic不存在的情况下,topic和他的队列只会在一个broker中创建,并且队列默认是创建4个

下文会对这个topic进行验证,也许是dashboard1.0的bug,在后续2.0中似乎已经被修复,但是现在研究的主要是4.9的版本,所以只能暂时先用1.0的版本
3,消息到底存在哪个broker
首先再次全部清空数据
shell
sudo rm -rf /usr/local/env/rocketmq/cluster/logs/*
sudo rm -rf /usr/local/env/rocketmq/cluster/store/*
再次递归创建目录commitlog,consumequeue和index,这里的目录需要对应前面docker-compose.yml中的挂载路径,如果不存在前面的挂载路径可能会启动失败
shell
sudo mkdir -p /usr/local/env/rocketmq/cluster/store/brokerA-master/{commitlog,consumequeue,index}
sudo mkdir -p /usr/local/env/rocketmq/cluster/store/brokerA-slave/{commitlog,consumequeue,index}
sudo mkdir -p /usr/local/env/rocketmq/cluster/store/brokerB-master/{commitlog,consumequeue,index}
sudo mkdir -p /usr/local/env/rocketmq/cluster/store/brokerB-slave/{commitlog,consumequeue,index}
对这些创建的目录给予权限,方便后续数据的增删改查
shell
sudo chmod -R 777 /usr/local/env/rocketmq/cluster/store
随后执行关闭docker容器的命令,将之前正在运行的broker给全部终止,并且重新启动
shell
sudo docker compose down
sudo docker compose up -d
对dashboard也重新启动,防止内部缓存的一些干扰性
java
cd /usr/local/env/rocketmq/cluster/bashboard
sudo bash stop-dashboard.sh
sudo bash start-dashboard.sh
执行完成后再次进入可以发现集群数据为空,主题数据不存在


接下来测试一个新的主题,叫做zhsTopicV2,测试一下数据到底是进入了哪个broker,到底是brokerA还是brokerB,dashboard到底存不存在误导性
java
for (int i = 0; i < 100000; i++) {
String uuid = UUID.randomUUID().toString();
uuid = uuid + uuid;
Message msg = new Message("zhsTopicV2", "TagA", uuid.getBytes());
producer.send(msg);
System.out.println("消息发送完成 uuid = " + uuid);
}
消息消费情况如下,此时只开了消费者和生产者
java
消息发送完成 uuid = 3472e88e-3502-4ef5-a28b-1ede892657333472e88e-3502-4ef5-a28b-1ede89265733
消息发送完成 uuid = 5cd14d5c-a4e5-4d72-b51a-b67c08325d2f5cd14d5c-a4e5-4d72-b51a-b67c08325d2f
随后在docker容器中查看消息到底存在哪个broker里面,首先进入brokerA-master的容器里面,发现啥也没有
shell
sudo docker exec -it a0306e902466 /bin/bash
cd /home/rocketmq/store-a-master/commitlog

但是在brokerB-master中,可以发现数据全部进入了里面,commitlog也有数据
shell
sudo docker exec -it 5ee67b299e2e /bin/bash
cd /home/rocketmq/store-b-master/commitlog

所以印证了上面那个,如果不干预topic的创建,那么topic会创建在nameserver的第一个注册进来的broker中,因此这里验证确实只在brokerB-master中,brokerA-master中不存在。所以这个dashboard界面只能当作参考,至少证明集群这些是链接成功的
在这需要对broker进行一个初步的总结:
- 1,rocketMq中的topic只是一个虚拟的逻辑概念,真实存储数据的还是queue
- 2,rocketmq中集群的全部broker,是一个分片概念,就是做一个水平扩容,一条消息也只会存在一个broker的一个queue中,这个是他的底层设计,这样有效解决一些堆积,重复消费等问题
- 3,在使用自动创建topic和队列时,那么消息只会存在naveserver注册的第一个broker中(RocketMQ 的默认策略(TBW102 路由)),topic和queue只会创建在这个broker中。
如果想要topic分布在所有的broker上,需要手动执行命令
java
mqadmin updateTopic -n namesrv:9876 -t zhsTopicV2 -c DefaultCluster -q 4 -r 4
4,消费者消费消息
上面已经完成了生产者的写入和broker得到变化,接下来主要是讲解rocketmq的消费者是如何消费,首先是编写消费的代码,其代码如下
java
package com.zhs.study.source;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
public class ConsumerDemo {
public static void main(String[] args) throws Exception {
// 1. 创建 Consumer(必须填写消费组名称)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("zhsConsumerGroup");
// 2. 指定 NameServer
consumer.setNamesrvAddr("192.168.1.2:9876");
// 3. 订阅 Topic 和 TagA
consumer.subscribe("zhsTopicV2", "TagA");
// 4. 注册消息监听器(并发消费)
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
System.out.println(
"收到消息:" + new String(msg.getBody()) +
", queueId=" + msg.getQueueId() +
", msgId=" + msg.getMsgId()
);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 5. 启动消费者
consumer.start();
System.out.println("Consumer 启动成功");
System.in.read();
}
}
执行完后,控制台打印的日志如下,可以看到消息内容,消息要被消费的队列id和消息msgId
java
Consumer 启动成功
收到消息:04c83296-d2ca-4e4e-b418-34777e138c1d04c83296-d2ca-4e4e-b418-34777e138c1d, queueId=1, msgId=7F000001561422D8CFE02344A3518695
收到消息:7df0b623-2067-4920-8bb1-fba8523656197df0b623-2067-4920-8bb1-fba852365619, queueId=1, msgId=7F000001561422D8CFE02344A326866D
收到消息:11ba985e-987d-4828-ae54-29990f40908811ba985e-987d-4828-ae54-29990f409088, queueId=1, msgId=7F000001561422D8CFE02344A32F8675
...
由于是通过main方法来消费消息,所以需要使用下面这段来保证和dashboard的长连接,保证程序不退出,那么dashboard就能展示数据
java'
System.in.read();
打开控制台的消费者页面,就可以看到这个刚刚创建的消费者组 zhsConsumerGroup ,这里可以证明的是消息已经完全被消费,但是存在的一定误导性,因为上面已经说过了消息只存在brokerB上面,brokerA上面并没有存数据的

消费者的消费情况如下
- 上面的RETRY表示重试的队列,由于都是成功消费,所以暂时没有出现重试的broker和队列,此时的brokerOffset和consumerOffset都是0
- 下面表示的是消费者的消费情况,上面已经分析了消息只会进入到brokerB对应的队列中,因此这里也会存在偏差,按理来说只有brokerB中的数据被消费
最后再查看一下集群的详情,可以发现BrokerA和BrokerB中,会显示今天生产消息的总数和消费消息的总数

5,集群同步情况
上面对普通消息有了初步的概念,接下来查看以下集群中同步的情况。上面已经说过由于是自动创建topic,所以只会在nameServer轮询的第一个机器中的第一个broker中创建topic和queue,因此目前是只有brokerB中有topic和queue,因此数据目前是只存储在brokerB中,brokerA中暂时没有。
接下来就是研究brokerB中集群同步的情况,brokerB也有一个主从复制的小集群,为了保证高可用和高性能,因此存在brokerB-master和brokerB-slave的两个结点。接下来主要就是对这两个节点进行研究,现在master节点是已经成功的进行了数据的生产和消费,现在就是对slave节点判断数据记录是否成功同步。
5.1,master和slave区别
在brokerB-slave结点的配置文件中,设置了brokerRole的角色为SLAVE,因此他就会主动的去拉取brokerB-master结点的数据,但是他不直接参与消费

在brokerB-master的配置文件中,设置了brokerRole的角色为ASYNC_MASTER ,说明在master结点中,会直接参与消息存储、消费、负载均衡 ,并且会异步将数据同步到 slave

接下来在dashboard控制台查看zhsTopicV2的信息,可以只有brokerA或者brokerB的详情,不会具体表面是master还是slave,在rocketMq设计阶段就已经表明,核心信息主要以master为主,slave不会直接参与master所执行的事情

因此可以得到以下信息,创建的queue和topic只会存在master结点上
| 角色 | 是否创建 queue | Producer 是否发送 | Consumer 是否消费 |
|---|---|---|---|
| Master | ✔ 创建 queue | ✔ 会发送到 Master | ✔ 只能从 Master 拉取 |
| Slave | ❌ 不创建 queue | ❌ Producer 不会发送 | ❌ Consumer 不会消费 |
5.2,salve同步master的情况
5.2.1,控制台查看同步情况
接下来直接在控制台验证主从同步的情况,进入集群页面,点击状态按钮查看状态

随后根据内容对比以下指标,可以发现同步时完全ok的
| 指标 | Master | Slave | 结论 |
|---|---|---|---|
| commitLogMaxOffset | 12350000 | 12350000 | 完全一致(强一致) |
| commitLogMinOffset | 0 | 0 | ✔ 一致 |
| dispatchBehindBytes | 0 | 0 | ✔ 无落后 |
| remainHowManyDataToFlush | 0 | 0 | ✔ 无待刷盘数据 |
| putMessageFailedTimes | 0 | 0 | ✔ 无失败 |
5.2.2,对比commitlog文件
上面这种方式时最常用的方式,但是对比commotlog时最精准的方式,根据之前docker-compose挂载的情况,以brokerB-master结点为例,可以得知这个这个store文件已经被挂载到了 /usr/local/env/rocketmq/cluster/store-a-master 目录下
shell
- /usr/local/env/rocketmq/cluster/store/brokerA-master:/home/rocketmq/store-b-master
接下来先看brokerB-master的commitLog文件,先进入容器内部,再查看commitlog的情况,此时的commitlog的大小是00000000001073741824
shell
sudo docker exec -it 5ee67b299e2e /bin/bash
cd /home/rocketmq/store-b-master/commitlog

接下来先看brokerB-slave的commitLog文件,先进入容器内部,再查看commitlog的情况,可以看到这个此时的commitlog的大小也是00000000001073741824,和master的一模一样,说明咱们的主从复制也是完全没问题的
shell
sudo docker exec -it 3ffd9d8dc986 /bin/bash
cd /home/rocketmq/store-b-slave/commitlog

这里的00000000001073741824大于是10g的大小,不是实际大小,而是预分配大小,可以直接通过du查看这个commitlog的实际大小
6,总结
在此篇完整的讲解了消息的基本使用,也是对前面的集群的搭建接进行了一个反馈,验证了前面的集群的搭建是没有问题的,同时也验证了dashboard没有那么的靠谱,还是需要进入服务内部去观察数据到底是如何存储和消费的。
RocketMQ4.x对应的Dashboard 统计的是Nameserver 路由缓存 + 客户端上报的 metrics 的数量,不是 commitlog 实际落盘量,它不是 consumequeue 真实偏移,也不是每个 broker 的真实消息数量 。