producer send源码_RocketMQ源码分析:Producer原理

1e31d0292bfaeba07d3ae1d116454d48.png

本文首发于Ressmix个人站点:https://www.tpvlog.com

通过前面几章的讲解,大家应该已经了解了NameServer和Broker的核心原理。我们来回顾一下:

首先NameServer启动后,内部会有一个核心的NamesrvController组件,控制NameServer的所有行为,包括启动一个Netty服务器去监听一个9876端口号,处理Broker和客户端发送过来的请求:

b15867f6bab67f8cf155f8c61d168052.png

接着,Broker启动之后,由内部的一个BrokerController组件管控Broker的整体行为,包括初始化Netty服务器用于接收客户端的网络请求、启动处理请求的线程池、执行定时任务的线程池、初始化核心功能组件等等,同时还会启动之后发送注册请求到NameServer去注册自己:

228b9cf278cb0b57262f633aee1bc877.png

最后,Broker启动之后会通过心跳与NameSever保持连接,NameServer有一个后台进程定时检查每个Broker的最近一次心跳时间,如果长时间没心跳就认为Broker已经故障,会将其剔除:

43ecf4b212f799d3c7be4821863c4bd1.png

到上述流程为止,RocketMQ集群就已经可以使用,我们可以通过Producer发送消息到MQ,本章我就来讲讲Producer的底层原理。

一、Producer创建

Producer的创建和使用非常简单,我们可以在example包下找到使用示例:

1// 创建生产者,并设置分组2DefaultMQProducer producer = new DefaultMQProducer("myGroup");3// 启动生产者4producer.start();

Producer启动后会进行初始化,但是并不会立即从NameServer拉取路由数据,只有当第一次发送消息时,才会根据消息的Topic拉取对应的路由数据。另外,其它很多核心逻辑,包括MessageQueue选择、跟Broker建立网络连接等,这些逻辑都是在Producer发送消息的时候才去执行。

Producer启动后的初始化方法没什么太多可以说的,核心逻辑都在首次发送消息时执行,所以本节不对初始化方法赘述。

二、发送消息

接着,我们来看下Producer发送消息的过程,消息必须指定Topic:

1Message msg = new Message("TopicTest","TagA", ("Hello RocketMQ").getBytes("UTF-8"));2SendResult sendResult = producer.send(msg);

关键看下send方法:

1@Override2public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {3    Validators.checkMessage(msg, this);4    msg.setTopic(withNamespace(msg.getTopic()));5    return this.defaultMQProducerImpl.send(msg);6}

层层往下,我们发现最终会调用DefaultMQProducerImpl.send()

1private SendResult sendDefaultImpl(Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {2    // ...3    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());45    // ...6    MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);7}

2.1 拉取路由信息

上述代码,我省略了很多无关紧要的逻辑,关键是tryToFindTopicPublishInfo这个方法,它会先从生产者本地查找是否有路由信息,没有再从NameServer拉取:

 1private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) { 2    // 从本地查询路由表 3    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic); 4 5    // 本地不存在,发送请求到NameServer查询 6    if (null == topicPublishInfo || !topicPublishInfo.ok()) { 7        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo()); 8        // 从NameServer拉取 9        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);10        topicPublishInfo = this.topicPublishInfoTable.get(topic);11    }1213    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {14        return topicPublishInfo;15    } else {16        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);17        topicPublishInfo = this.topicPublishInfoTable.get(topic);18        return topicPublishInfo;19    }20}

上述的updateTopicRouteInfoFromNameServer方法是关键,它的核心流程是封装一个Request请求对象,然后通过底层的Netty客户端发送请求到NameServer,最后会接收到一个Response响应对象, 然后它会从Response响应对象里提取Topic路由数据,更新到自己的本地缓存里:

54d95bd0144a4fce4ba51c5318d0c9d2.png

这里我就不再贴源码了,读者完全可以按照上述思路自己去源码里看,搞清了主干逻辑,源码其实很好理解。

2.2 选择MessageQueue

基础篇中我已经对RocketMQ的架构和基本原理作了详细讲解,那么大家应该知道Topic只是逻辑上的概念,消息最终是分布在Broker的各个MessageQueue中的。所以,Producer拉取完路由信息后,需要选择将消息发送到哪个MessageQueue中,知道了MessageQueue后,只要与MessageQueue所在的Broker建立连接就可以发送消息了。

bf97f44b08e3fb638e5ddea183b0adfe.png

DefaultMQProducerImpl.sendDefaultImpl()方法中,有一个selectOneMessageQueue方法,这行代码其实就是选择Topic中的一个MessageQueue,然后发送消息到这个MessageQueue去:

1private SendResult sendDefaultImpl(Message msg, final CommunicationMode communicationMode, final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {2    // ...3    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());45    // ...6    MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);7}

我们来看下selectOneMessageQueue方法的细节,我省略了一些Broker的故障回避机制代码,下面代码的核心逻辑就是:用一个自增的index对Topic的MessageQueue列表进行取模,获取到一个MessageQueue列表的位置,然后返回这个位置的MessageQueue:

 1int index = tpInfo.getSendWhichQueue().getAndIncrement(); 2for (int i = 0; i  3    int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size(); 4    if (pos 0)
5        pos = 0;
6    MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
7    if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
8        if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
9            return mq;
10    }
11}

对于MessageQueue的选择本质是一种负载均衡策略,上述代码其实就是一种最简单的负载均衡策略。

2.3 消息发送

选择完MessageQueue之后,就可以发送数据了,下面是DefaultMQProducerImpl.sendDefaultImpl()中的一行代码:

1sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);

在这个sendKernelImpl方法里,会通过brokerName从本地缓存查找Broker的实际地址,如果找不到,就从NameServer拉取Topic的路由数据,然后再次缓存到本地,有这个Broker地址后,就可以跟Broker进行网络通信了:

1String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());2if (null == brokerAddr) {3    tryToFindTopicPublishInfo(mq.getTopic());4    brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());5}

后面的逻辑,就是封装一个Request请求,并给消息分配全局唯一ID、对超过4KB的消息体进行压缩等等。Request中会包含生产者组、Topic名称、Topic的MessageQueue数量、MessageQueue的ID、消息发送时间、消息的flag、消息重试次数、prepared标记(事务消息)等等信息。

最后,Producer在底层是基于Netty与Broker建立长连接, 然后基于长连接进行持续通信,这样就可以把请求发送到指定的Broker上去了:

48747e0f644aafad67157d27db0c82eb.png

三、总结

本章,我对Producer发送消息的底层源码进行了分析,我们要知道Producer发送消息时的主体流程是先拉取Topic路由信息,然后选择MessageQueue,最后进行基于底层的Netty组件进行消息发送。很多源码的细节我们可以忽略掉,如果大家想要研究更加细致的源码细节,可以按照我上述讲解的流程,通过IDE去阅读对应的源码即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值