大纲
1.Producer作为生产者是如何创建出来的
2.Producer启动时是如何准备好相关资源的
3.Producer是如何从拉取Topic元数据的
4.Producer是如何选择MessageQueue的
5.Producer与Broker是如何进行网络通信的
6.Broker收到一条消息后是如何存储的
7.Broker是如何实时更新索引文件的
8.Broker是如何实现同步刷盘以及异步刷盘的
9.Broker是如何清理存储较久的磁盘数据的
10.Consumer作为消费者是如何创建和启动的
11.消费者组的多个Consumer会如何分配消息
12.Consumer会如何从Broker拉取一批消息
1.Producer作为生产者是如何创建出来的
(1)NameServer的启动
(2)Broker的启动
(3)Broker的注册和心跳
(4)通过Producer发送消息
(1)NameServer的启动
NameServer启动后的核心架构,如下图示:

NameServer启动后,会有一个NamesrvController组件管理控制NameServer的所有行为,包括内部会启动一个Netty服务器去监听一个9876端口号,然后接收处理Broker和客户端发送过来的请求。
(2)Broker的启动
Broker启动后的核心架构,如下图示:

Broker启动后,也会有一个BrokerController组件管理控制Broker的整体行为,包括初始化Netty服务器用于接收客户端的网络请求、启动处理请求的线程池、执行定时任务的线程池、初始化核心功能组件,同时还会发送注册请求到NameServer去注册自己。
(3)Broker的注册和心跳
Broker启动后,会向NameServer进行注册和定时发送注册请求作为心跳。NameServer会有一个后台进程定时检查每个Broker的最近一次心跳时间,如果长时间没心跳就认为Broker已经故障。如下图示:

(4)通过Producer发送消息
假设RocketMQ集群已经启动好了NameServer,而且还启动了一批Broker,同时Broker都已经把自己注册到NameServer里去了,NameServer也会定时检查这批Broker是否存活。那么就可以让开发好的业务系统去发送消息到RocketMQ集群里,于是需要创建一个Producer实例。
实际上我们开发好的系统,最终都需要创建一个Producer实例,然后通过Producer实例发送消息到RocketMQ的Broker上去。
下面是使用Producer实例发送消息到RocketMQ的代码,可以看到Producer是如何构造出来的。
DefaultMQProducer producer = new DefaultMQProducer("order_producer_group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
构造Producer的过程很简单:也就是创建一个DefaultMQProducer对象实例。在构造方法中,首先会传入所属的Producer分组,然后设置一下NameServer的地址,最后调用它的start()方法启动这个Producer即可。
创建DefaultMQProducer对象实例是一个非常简单的过程:就是创建出一个对象,然后保存它的Producer分组。设置NameServer地址也是一个很简单的过程,就是保存一下NameServer地址。
所以,最关键的还是调用DefaultMQProducer的start()方法去启动Producer这个消息生产者。
2.Producer启动时是如何准备好相关资源的
(1)DefaultMQProducer的start()方法
(2)Producer在第一次向Topic发送消息时才拉取Topic的路由数据
(3)Producer在第一次向Broker发送消息时才与Broker建立网络连接
(1)DefaultMQProducer的start()方法
接下来分析Producer在启动时是如何准备好相关资源的。Producer内部必须要有独立的线程资源,以及需要和Broker已经建立好网络连接,这样才能把消息发送出去。
在构造Producer时,它内部便会构造一个真正用于执行消息发送逻辑的DefaultMQProducerImpl组件。所以,真正的Producer生产者其实是这个DefaultMQProducerImpl组件。那么这个组件在启动的时都干了什么呢?
public class DefaultMQProducer extends ClientConfig implements MQProducer {
protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
...
@Override
public void start() throws MQClientException {
this.setProducerGroup(withNamespace(this.producerGroup));
this.defaultMQProducerImpl.start();
if (null != traceDispatcher) {
try {
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}
...
}
public class DefaultMQProducerImpl implements MQProducerInner {
...
public void start() throws MQClientException {
this.start(true);
}
public void start(final boolean startFactory) throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
this.serviceState = ServiceState.START_FAILED;
this.checkConfig();
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();
}
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup() + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), null);
}
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
if (startFactory) {
mQClientFactory.start();
}
log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(), this.defaultMQProducer.isSendMessageWithVIPChannel());
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The producer service state not OK, maybe started once, " + this.serviceState + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), null);
default:
break;
}
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
this.startScheduledTask();
}
...
}
其实,上述start()方法的具体逻辑暂时不需要深入分析,因为其中的逻辑并没有直接与Producer发送消息相关联。比如拉取Topic的路由数据、选择MessageQueue、跟Broker建立长连接、发送消息到Broker等这些核心逻辑,其实都封装在发送消息的方法中。
(2)Producer在第一次向Topic发送消息时才拉取Topic的路由数据
假设后续Producer要发送消息,那么就要指定往哪个Topic发送消息。因此Producer需要知道Topic的路由数据,比如Topic有哪些MessageQueue,每个MessageQueue在哪些Broker上。如下图示:

从start()方法源码可知,在Producer启动时,并不会去拉取Topic的路由数据。实际上,Producer在第一次向Topic发送消息时,才会去拉取Topic的路由数据。包括这个Topic有几个MessageQueue、每个MessageQueue在哪个Broker上。然后从中选择一个MessageQueue,接着与对应的Broker建立网络连接,最后才把消息发送过去。
(3)Producer在第一次向Broker发送消息时才与Broker建立网络连接
从start()方法源码可知,在Producer启动时,并不会和所有Broker建立网络连接。很多核心的逻辑,包括拉取Topic路由数据、选择MessageQueue、和Broker建立网络连接等,都是在Producer第一次发送消息时才进行处理的。
3.Producer是如何从拉取Topic元数据的
(1)Producer发送消息的方法
(2)Producer拉取Topic路由数据的过程
(1)Producer发送消息的方法
当调用Producer的send()方法发送消息时,最终会调用到DefaultMQProducerImpl的sendDefaultImpl()方法。
在sendDefaultImpl()方法里,开始会有一行非常关键的代码,如下所示:
public class DefaultMQProducerImpl implements MQProducerInner {
...
private SendResult sendDefaultImpl(Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
...
TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
...
}
...
}
该行代码的意思是,每次Producer发送消息时,都会先检查一下要发送消息的那个Topic的路由数据是否在本地。如果不在,才会发送请求到NameServer去拉取Topic的路由数据,然后缓存在本地。
(2)Producer拉取Topic路由数据的过程
进入tryToFindTopicPublishInfo()方法,会发现其逻辑非常简单:就是会先检查一下自己本地是否有这个Topic的路由数据的缓存,如果没有就发送网络请求到NameServer去拉取,如果有就直接返回本地Topic路由数据缓存,如下图示:

那么Producer是如何发送网络请求到NameServer去拉取Topic路由数据的呢?这其实就对应了tryToFindTopicPublishInfo()方法内的一行代码,如下所示:
public class DefaultMQProducerImpl implements MQProducerInner {
...
private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
if (null == topicPublishInfo || !topicPublis

最低0.47元/天 解锁文章
1118

被折叠的 条评论
为什么被折叠?



