RocketMQ 5.x源码分析实战(一)

背景

        使用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是一样的,说明就是消费了刚刚生产的第一条消息😁

在这其实是有阅读大佬的博客带来的启发,感谢大佬分享

大家心心念念的RocketMQ5.x入门手册来喽_rocketmq5 教程-CSDN博客

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值