RocketMQ 发送普通消息源码分析 (02)

1 生产端发送消息

2 发送消息流程

  1. 一个是可以批量发送的流程
  2. 一个是直接发送,

以直接发送的方法为节点,只分析批量发送的流程。之后再分析直接发送的流程

    @Override
    public SendResult send(
        Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        msg.setTopic(withNamespace(msg.getTopic()));
        if (this.getAutoBatch() && !(msg instanceof MessageBatch)) {
            return sendByAccumulator(msg, null, null); // TODO 批量发送
        } else {
            return sendDirect(msg, null, null);  // TODO 直接发送
        }
    }

DefaultMQProducer#sendByAccumulator

  1. 如果不能批量发送则走直接发送的方法 sendDirect
  2. 如果走的是批量发送,则将消息发送到生产者积累器中 produceAccumulator

org.apache.rocketmq.client.producer.ProduceAccumulator#send(org.apache.rocketmq.common.message.Message, org.apache.rocketmq.common.message.MessageQueue, org.apache.rocketmq.client.producer.SendCallback, org.apache.rocketmq.client.producer.DefaultMQProducer)


    public SendResult sendByAccumulator(Message msg, MessageQueue mq,
        SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        // check whether it can batch
        if (!canBatch(msg)) { //TODO 判断是否需要进行批量操作发送
            return sendDirect(msg, mq, sendCallback); //TODO 直接发送
        } else {
            Validators.checkMessage(msg, this);
            MessageClientIDSetter.setUniqID(msg); // TODO 设置唯一的msg的ID
            if (sendCallback == null) {
                return this.produceAccumulator.send(msg, mq, this);
            } else {
                this.produceAccumulator.send(msg, mq, sendCallback, this); //  TODO 将消息发送到生产者积累器 produceAccumulator 中
                return null;
            }
        }
    }
ProduceAccumulator#send
  1. 将msg添加到消息积累器中MessageAccumulation
  2. 如果添加成果成功,直接退出死循环
  3. 如果添加批量不成功,说明消息积累器已经满足发送的条件需要发送消息,清空批量缓存
  4. 添加成功之后跳出while (true)
  void send(Message msg, MessageQueue mq,
        SendCallback sendCallback,
        DefaultMQProducer defaultMQProducer) throws InterruptedException, RemotingException, MQClientException {
        AggregateKey partitionKey = new AggregateKey(msg, mq);
        while (true) {
            MessageAccumulation batch = getOrCreateAsyncSendBatch(partitionKey, defaultMQProducer);
            if (!batch.add(msg, sendCallback)) { // TODO  将消息发送添加到 MessageAccumulation 消息积累器中
                asyncSendBatchs.remove(partitionKey, batch);
            } else {
                return;
            }
        }
    }

MessageAccumulation#add (将消息添加到消息积累器中)

  1. 将消息缓存到LinkedList
  2. 刷新消息积累器中的消息实际容量
  3. 判断是否准备好发送,准备好发送,进行直接发送

        public boolean add(Message msg,
            SendCallback sendCallback) throws InterruptedException, RemotingException, MQClientException {
            synchronized (this.closed) {
                if (this.closed.get()) {
                    return false;
                }
                this.count++;
                this.messages.add(msg); // TODO 存放List缓存中
                this.sendCallbacks.add(sendCallback);
                messagesSize.getAndAdd(msg.getBody().length); //TODO  记录现有的积累器中消息的存储长度
            }
            if (readyToSend()) { // TODO 准备好 去发送
                this.send(sendCallback); // TODO 真正发送消息的位置
            }
            return true;

        }

消息的实际长度 大于 持有消息长度的值 或者消息的消息积累器的的实际存在时间大于实际持有时间,则进行清理消息积累器中的消息 发送消息

3 直接发送消息的源码分析

3.1 入口

DefaultMQProducerImpl#sendDefaultImpl 

  • 获取主题:获取发布的主题信息。this.tryToFindTopicPublishInfo(msg.getTopic())
  • 选择发送的队列 selectOneMessageQueue
  • 重试逻辑:重试次数 异步的重试次数是1
        long costTime = beginTimestampPrev - beginTimestampFirst;
  • 投递超时:投递消息的超时时间 则退出重试循环,不在进行重试投递消息(包括首次发送消息)
  • 发送消息 (后续发送消息的流程)

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

 private SendResult sendDefaultImpl(
        Message msg,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final long timeout
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        this.makeSureStateOK();
        Validators.checkMessage(msg, this.defaultMQProducer);
        final long invokeID = random.nextLong();
        long beginTimestampFirst = System.currentTimeMillis();
        long beginTimestampPrev = beginTimestampFirst;
        long endTimestamp = beginTimestampFirst;
        // todo 1 获取发布的主题信息
        TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic()); 
        if (topicPublishInfo != null && topicPublishInfo.ok()) {
            boolean callTimeout = false;
            MessageQueue mq = null;
            Exception exception = null;
            SendResult sendResult = null;
// TODO 2 重试次数 异步的重试次数是1
            int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1; 
            int times = 0;
            String[] brokersSent = new String[timesTotal];
            boolean resetIndex = false;
            for (; times < timesTotal; times++) {
                String lastBrokerName = null == mq ? null : mq.getBrokerName();
                if (times > 0) {
                    resetIndex = true;
                }
           //TODO  3 选出一个需要发送的队列
                MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName, resetIndex); 
                if (mqSelected != null) {
                    mq = mqSelected;
                    brokersSent[times] = mq.getBrokerName();
                    try {
                        beginTimestampPrev = System.currentTimeMillis();
                        if (times > 0) {
                            //Reset topic with namespace during resend.
                            msg.setTopic(this.defaultMQProducer.withNamespace(msg.getTopic()));
                        }
                        long costTime = beginTimestampPrev - beginTimestampFirst; // TODO 投递消息的超时时间 则退出重试循环,不在进行重试投递消息(包括首次发送消息)、
                        if (timeout < costTime) {
                            callTimeout = true;
                            break;
                        }
                        // TODO 发送消息
                        sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                        endTimestamp = System.currentTimeMillis();
                        this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true);
                        switch (communicationMode) {
                            case ASYNC:
                                return null;
                            case ONEWAY:
                                return null;
                            case SYNC:
                                if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
                                    if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
                                        continue;
                                    }
                                }

                                return sendResult;
                            default:
                                break;
                        }
                    } catch (MQClientException e) {
                       ......

    }

3.2 获取发布的主题路由信息

  • 从缓存的主题发布信息中过去指定主题信息
  • 如果缓存没有,从nameServer中刷新主题信息到缓存中
  • 如果获取的主题信息中路由信息不正常(没有包含路由信息),也需要从NameServer中刷新缓存
  • 需要分析从NameServer获取主题路由信息的点:
    updateTopicRouteInfoFromNameServer

从NameServe中更新主题路由信息

  1. 默认主题的分支,需要逻辑判断默认 主题的可读可写队列的数量,可读队列和生产者默认的队列数量中取最小值
  2. 不走默认主题:直接去NameServe获取主题路由数据
    this.mQClientAPIImpl.getTopicRouteInfoFromNameServer

this.mQClientAPIImpl.getTopicRouteInfoFromNameServer方法最终调用啦Netty API 从NameServe获取主题路由信息

这是获取主题的直接通信的逻辑,发送消息的通信逻辑和此处类似。

4 借鉴点

  1. 对于Netty的应用:RocketMQ对Netty按照自己的领域概念封装成remote远程调用领域

    所有对远程通信的地方都是切换成和remoting模块的交互
  2. 对消息的发送:分为直接发送和批量发送,利用消息积累器对消息进行积累进行发送,按照

    消息大小和存储的时间实现对消息的批量发送
  3. 对发送重试逻辑的实现逻辑可以参考

5 下节内容

  1. Broker和Producer的建立长链接 
  2. Broker 接受消息投递和消息持久化索引化的逻辑

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值