RocketMq源码分析(一):源码环境的搭建

369 阅读5分钟

highlight: a11y-dark

一.rocketmq的架构

在搭建mq进行源码调试前,我们需要知道mq的整体架构,这样才会方便我们进行后面深入的学习,mq的架构如下图所示

image.png

  • Producer:消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。

  • Consumer:消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制,可以满足大多数用户的需求。

  • NameServer:NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能:

    • Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
    • 路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

    NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息。

  • BrokerServer:Broker主要负责消息的存储、投递和查询以及服务高可用保证,为了实现这些功能,Broker包含了以下几个重要子模块:

    • Remoting Module:整个Broker的实体,负责处理来自clients端的请求。

      • Client Manager:负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅信息
      • Store Service:提供方便简单的API接口处理消息存储到物理硬盘和查询功能。
      • HA Service:高可用服务,提供Master Broker 和 Slave Broker之间的数据同步功能。
      • Index Service:根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询。

二.环境搭建

2.1.官网地址

rocketmq.apache.org/docs/quickS… mq的githup的地址是github.com/apache/rock…, 但是速度比较慢,可以fork到gitee上, 我的gitee地址是gitee.com/helloworldq…

本人做源码分析,是在release-5.0.0分支

2.2 本地启动需要修改的配置

本地启动,需要指定配置文件的位置,有如下几点

2.2.1 复制config文件

image.png

mq项目的配置文件,默认在 rocketmq/distribution模块下的conf目录中,我在项目中创建conf路径,可以把这四个文件复制到conf路劲下

2.2.2 启动nameserv

nameServer 的主类为org.apache.rocketmq.namesrv.NamesrvStartup 在启动的时候,配置mq_home的地址为ROCKETMQ_HOME=D:\D\code\learn\rocketmq

image.png 启动成功后,可以看到控制台信息:

image.png

2.2.3 启动broker

image.png broker启动需要配置

  • broker.conf的地址和 nameserv的地址

-c D:\D\code\learn\rocketmq\conf\broker.conf -n 127.0.0.1:9876

  • rocketmq_home地址

    ROCKETMQ_HOME=D:\D\code\learn\rocketmq

broker启动成功后,控制台显示

image.png

2.3 测试消息发送

image.png 消息发送测试,可以在 example/quickstart 里面找到producer类,

public class Producer {

/**
 * The number of produced messages.
 */
public static final int MESSAGE_COUNT = 1000;
public static final String PRODUCER_GROUP = "please_rename_unique_group_name";
public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876";
public static final String TOPIC = "TopicTest";
public static final String TAG = "TagA";

public static void main(String[] args) throws MQClientException, InterruptedException {

    /*
     * Instantiate with a producer group name.
     */
    DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);

    /*
     * Specify name server addresses.
     *
     * Alternatively, you may specify name server addresses via exporting environmental variable: NAMESRV_ADDR
     * <pre>
     * {@code
     *  producer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
     * }
     * </pre>
     */
    // Uncomment the following line while debugging, namesrvAddr should be set to your local address
    producer.setNamesrvAddr(DEFAULT_NAMESRVADDR);

    /*
     * Launch the instance.
     */
    producer.start();

    for (int i = 0; i < 1; i++) {
        try {

            /*
             * Create a message instance, specifying topic, tag and message body.
             */
            Message msg = new Message(TOPIC ,TAG, ("Hello RocketMQ 同步 " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            Message msgOneway = new Message(TOPIC ,TAG, ("Hello RocketMQ oneway方式" + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            Message msgAsc = new Message(TOPIC ,TAG, ("Hello RocketMQ 异步" + i).getBytes(RemotingHelper.DEFAULT_CHARSET));


            /**
             * 方式1: 同步发送
             */
            SendResult sendResult = producer.send(msg);


            /**
             * 方式2:由于在oneway方式发送消息时没有请求应答处理,如果出现消息发送失败,则会因为没有重试而导致数据丢失。若数据不可丢,建议选用可靠同步或可靠异步发送方式。
             */
             producer.sendOneway(msgOneway);



            /**
             * 方式3:异步发送
             *  if you want to get the send result in a asynchronize way, you can use this send method
             */
            producer.send(msgAsc, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    System.out.printf("%异步发送结果:成功==>"+  sendResult.getMsgId());
                }
                @Override
                public void onException(Throwable e) {
                    System.out.printf("%异步发送结果:失败==>"+  e.getMessage());
                }
            });



            System.out.printf("消息发送成功==>%s%n", sendResult);
        } catch (Exception e) {
            e.printStackTrace();
            Thread.sleep(1000);
        }
    }

    /*
     * Shut down once the producer instance is no longer in use.
     */
    producer.shutdown();
}

}

运行成功,可以看到控制台

消息发送成功==>SendResult [sendStatus=SEND_OK, msgId=7F000001468818B4AAC26B1966920000, offsetMsgId=7F00000100002A9F000000000003E718, messageQueue=MessageQueue [topic=TopicTest, brokerName=broker-a, queueId=1], queueOffset=263] 19:07:07.396 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[127.0.0.1:9876] result: true 19:07:07.401 [NettyClientSelector_1] INFO RocketmqRemoting - closeChannel: close the connection to remote address[127.0.0.1:10911] result: true

2.4 启动消费端

同理,消费端和生产者在一起的,启动后

public class Consumer {

public static final String CONSUMER_GROUP = "please_rename_unique_group_name_4";
public static final String DEFAULT_NAMESRVADDR = "127.0.0.1:9876";
public static final String TOPIC = "TopicTest";

public static void main(String[] args) throws MQClientException {

    /*
     * Instantiate with specified consumer group name.
     */
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP);

    /*
     * Specify name server addresses.
     * <p/>
     *
     * Alternatively, you may specify name server addresses via exporting environmental variable: NAMESRV_ADDR
     * <pre>
     * {@code
     * consumer.setNamesrvAddr("name-server1-ip:9876;name-server2-ip:9876");
     * }
     * </pre>
     */
    // Uncomment the following line while debugging, namesrvAddr should be set to your local address
    consumer.setNamesrvAddr(DEFAULT_NAMESRVADDR);

    /*
     * Specify where to start in case the specific consumer group is a brand-new one.
     */
    consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

    /*
     * Subscribe one more topic to consume.
     * 订阅一个或多个topic,并指定tag过滤条件,这里指定*表示接收所有tag的消息
     */
    consumer.subscribe(TOPIC, "*");

    /*
     *  Register callback to execute on arrival of messages fetched from brokers.
     */
    consumer.registerMessageListener((MessageListenerConcurrently) (msg, context) -> {
        System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msg);

        for (MessageExt messageExt : msg) {
            System.out.println("具体数据为==>"+new String(messageExt.getBody()));
        }
        System.out.println();
        // 返回消息消费状态,ConsumeConcurrentlyStatus.CONSUME_SUCCESS为消费成功
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    });

    /*
     *  Launch the consumer instance.
     */
    consumer.start();

    System.out.printf("Consumer Started.%n");
}

}

image.png 消费者启动成功后,可以消费到生产者刚才发送的消息

三.总结

本章主要讲解了mq的本地启动流程,和收发消息测试,接下来我们将详细讲解每一部分的重要源码