背景
使用RocketMQ已经许久,但是对于此消息中间件每次遇到问题都不能很好的追寻归因,又甚至是盲人摸象一般不断到处找资料猜测性的分析bug,对于原理一知半解,因此想系统性的学习下RocketMQ,此前一直使用4.x版本,在23年7月份时,我们的系统升级到了5.1.4版本,因此现在以目前最新的版本进行学习。此博客寄希望于记录作为小白学习过程踩坑的心路历程~~
源码启动
本个章节的目的主要想拉取源码并启动起来,发出第一条消息
源码拉取:https://github.com/apache/rocketmq.git
拉取成功后,自行mavan clean install
Tips:电脑性能好的小伙伴,可以加mvn clean install -T 16 -Dmaven.test.skip=true进行加速编译yo
环境准备
1. 创建所需要的home目录,并拷贝distribution下的conf文件到你的home目录下,如图所示
我这里是/Users/mock/IdeaProjects/rocketmq/home
启动namesrv
找到namesrv目录下的NamesrvStartup直接启动
你会收获一枚报错^o^
ok,这没问题,是正常的,就是让我们配置环境变量
跟着我,我们继续
打开Idea->Edit Configurations
把前面提到的自己创建的home目录进行填入到Environment variables中,and then click OK
ROCKETMQ_HOME=/Users/mock/IdeaProjects/rocketmq/home
restart again,then(^_^)
The Name Server boot success. serializeType=JSON, address 0.0.0.0:9876
启动broker
启动broker其实和namesrv是一样的,也需要配置环境变量,但是多了个启动命令
-c /Users/mock/IdeaProjects/rocketmq/home/config/broker.conf -n 127.0.0.1:9876
ROCKETMQ_HOME=/Users/mock/IdeaProjects/rocketmq/home
broker配置文件补充说明
这里我想补充说明下为什么要添加 -n 127.0.0.1:9876
其实在broker.conf添加namesrvAddr=127.0.0.1:9876也是一样的(主要是作者未对broker.conf提供默认的namesrvAddr这个值,我一直认为官网提供的不会有问题,在这踩坑好久😭😭😭😭😭,并且找了很多资料也没说这个要配置,还是看了broker启动源码才发现不对劲)
在BrokerStartup.java阅读发现这个是namesrv的地址,如果不添加的话,会导致即使你启动了broker,但其实并不会在namesrv上有任何注册信息
启动控制台显示
可以借助下mqadmin命令看看当前的集群状态
如果不配置会发生什么呢,主要体现在proxy启动的时候,就一定会报错create system broadcast topic DefaultHeartBeatSyncerTopic failed on cluster DefaultCluster
然而这个报错,在github或者是目前可搜索的教程中是没人告诉你的,因为broker在启动没报错过,但是你看代码也不会觉得proxy有问题的!!(在这里怀疑了proxy有问题很久,去看issues也有同样报错,但是人家不是这个问题,可能是因为我太菜了😭😭,别骂我😭😭😭)
org.apache.rocketmq.proxy.common.ProxyException: create system broadcast topic DefaultHeartBeatSyncerTopic failed on cluster DefaultCluster
at org.apache.rocketmq.proxy.service.sysmessage.AbstractSystemMessageSyncer.createSysTopic(AbstractSystemMessageSyncer.java:177)
at org.apache.rocketmq.proxy.service.sysmessage.AbstractSystemMessageSyncer.start(AbstractSystemMessageSyncer.java:143)
at org.apache.rocketmq.proxy.service.client.ClusterConsumerManager.start(ClusterConsumerManager.java:68)
at org.apache.rocketmq.common.utils.AbstractStartAndShutdown.start(AbstractStartAndShutdown.java:33)
at org.apache.rocketmq.common.utils.AbstractStartAndShutdown.start(AbstractStartAndShutdown.java:33)
at org.apache.rocketmq.common.utils.AbstractStartAndShutdown.start(AbstractStartAndShutdown.java:33)
at org.apache.rocketmq.proxy.ProxyStartup.main(ProxyStartup.java:96)
启动proxy
启动proxy也是和前面一样的步骤
加载环境变量:RMQ_PROXY_HOME=/Users/mock/IdeaProjects/rocketmq/home/config/proxy/
⚠️注意是RMQ_PROXY_HOME 不是ROCKETMQ_HOME
到此已经启动完成基本的MQ发送所需要的组件
发送第一条消息
1.创建一个额外的rocketmq-client的pom工程
2.引入rocketmq-client-api依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client-apis</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client-java</artifactId>
<version>5.0.0</version>
</dependency>
3.在源码目录中MQAdminStartup启动类,加入创建测试topic到broker中
updatetopic -n localhost:9876 -t TestTopic -c DefaultCluster
运行创建成功
create topic to 192.168.3.2:10911 success.
TopicConfig [topicName=TestTopic, readQueueNums=8, writeQueueNums=8, perm=RW-, topicFilterType=SINGLE_TAG, topicSysFlag=0, order=false, attributes={}]
请求代码
package com.example.rocketmqdemo; import org.apache.rocketmq.client.apis.ClientConfiguration; import org.apache.rocketmq.client.apis.ClientServiceProvider; import org.apache.rocketmq.client.apis.SessionCredentialsProvider; import org.apache.rocketmq.client.apis.StaticSessionCredentialsProvider; import org.apache.rocketmq.client.apis.message.Message; import org.apache.rocketmq.client.apis.producer.Producer; import org.apache.rocketmq.client.apis.producer.SendReceipt; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.UUID; import java.util.concurrent.CompletableFuture; public class RocketMQProducer { public static void main(String[] args) throws Exception { final ClientServiceProvider provider = ClientServiceProvider.loadService(); // Credential provider is optional for client configuration. String accessKey = "RocketMQ"; String secretKey = "12345678"; SessionCredentialsProvider sessionCredentialsProvider = new StaticSessionCredentialsProvider(accessKey, secretKey); String endpoints = "127.0.0.1:8081"; ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder() .setEndpoints(endpoints) .setCredentialProvider(sessionCredentialsProvider) .setRequestTimeout(Duration.ofSeconds(30)) .build(); String topic = "TestTopic"; final Producer producer = provider.newProducerBuilder() .setClientConfiguration(clientConfiguration) // Set the topic name(s), which is optional. It makes producer could prefetch the topic route before // message publishing. .setTopics(topic) // May throw {@link ClientException} if the producer is not initialized. .build(); // Define your message body. byte[] body = "This is a normal message for Apache RocketMQ".getBytes(StandardCharsets.UTF_8); String tag = "TagA"; UUID uuid = UUID.randomUUID(); final Message message = provider.newMessageBuilder() // Set topic for the current message. .setTopic(topic) // Message secondary classifier of message besides topic. .setTag(tag) // Key(s) of the message, another way to mark message besides message id. .setKeys(uuid+"-0e094a5f9d85") .setBody(body) .build(); final CompletableFuture<SendReceipt> future = producer.sendAsync(message); future.whenComplete((sendReceipt, throwable) -> { if (null == throwable) { System.out.println("Send message successfully, messageId=" + sendReceipt.getMessageId()); } else { System.out.println("Failed to send message"); } }); // Block to avoid exist of background threads. Thread.sleep(Long.MAX_VALUE); // Close the producer when you don't need it anymore. producer.close(); } }
运行
发送成功啦!😁
消费第一条消息
创建个消费者RocketMQConsumer
package com.example.rocketmqdemo; import org.apache.rocketmq.client.apis.ClientConfiguration; import org.apache.rocketmq.client.apis.ClientConfigurationBuilder; import org.apache.rocketmq.client.apis.ClientException; import org.apache.rocketmq.client.apis.ClientServiceProvider; import org.apache.rocketmq.client.apis.SessionCredentialsProvider; import org.apache.rocketmq.client.apis.StaticSessionCredentialsProvider; import org.apache.rocketmq.client.apis.consumer.ConsumeResult; import org.apache.rocketmq.client.apis.consumer.FilterExpression; import org.apache.rocketmq.client.apis.consumer.FilterExpressionType; import org.apache.rocketmq.client.apis.consumer.PushConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.time.Duration; import java.util.Collections; public class RocketMQConsumer { private static final Logger logger = LoggerFactory.getLogger(RocketMQConsumer.class); public static void main(String[] args) throws ClientException, IOException, InterruptedException { final ClientServiceProvider provider = ClientServiceProvider.loadService(); // 接入点地址,需要设置成Proxy的地址和端口列表,一般是xxx:8081;xxx:8081。 String endpoints = "127.0.0.1:8081"; ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder() .setEndpoints(endpoints) .setRequestTimeout(Duration.ofSeconds(30)) .build(); // 订阅消息的过滤规则,表示订阅所有Tag的消息。 String tag = "*"; FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG); // 为消费者指定所属的消费者分组,Group需要提前创建。 String consumerGroup = "CustomerGroupA"; // 指定需要订阅哪个目标Topic,Topic需要提前创建。 String topic = "TestTopic"; // 初始化PushConsumer,需要绑定消费者分组ConsumerGroup、通信参数以及订阅关系。 PushConsumer pushConsumer = provider.newPushConsumerBuilder() .setClientConfiguration(clientConfiguration) // 设置消费者分组。 .setConsumerGroup(consumerGroup) // 设置预绑定的订阅关系。 .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression)) // 设置消费监听器。 .setMessageListener(messageView -> { // 处理消息并返回消费结果。 logger.info("Consume message successfully, messageId={}", messageView.getMessageId()); return ConsumeResult.SUCCESS; }) .build(); // Thread.sleep(Long.MAX_VALUE); // 如果不需要再使用 PushConsumer,可关闭该实例。 // pushConsumer.close(); } }
消费成功啦,并且msgId是一样的,说明就是消费了刚刚生产的第一条消息😁
在这其实是有阅读大佬的博客带来的启发,感谢大佬分享