highlight: a11y-dark
一.rocketmq的架构
在搭建mq进行源码调试前,我们需要知道mq的整体架构,这样才会方便我们进行后面深入的学习,mq的架构如下图所示
-
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文件
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
启动成功后,可以看到控制台信息:
2.2.3 启动broker
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启动成功后,控制台显示
2.3 测试消息发送
消息发送测试,可以在 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");
}
}
消费者启动成功后,可以消费到生产者刚才发送的消息
三.总结
本章主要讲解了mq的本地启动流程,和收发消息测试,接下来我们将详细讲解每一部分的重要源码