RocketMq源碼解讀(四)消息發送

前兩章內容中,介紹了Producer的啓動過程,然後是如何創建topic,本章內容將主要集中在如何發送消息到broker 

 一、消息的投遞

消息的投遞分爲普通消息的投遞和事務消息的投遞

public class DefaultMQProducer extends ClientConfig implements MQProducer{
    // 所有的實際操作委託給這個實現
    protected final transient DefaultMQProducerImpl defaultMQProducerImpl;
    // 默認topic隊列數
    private volatile int defaultTopicQueueNums = 4;
    private int sendMsgTimeout = 3000;
    private int compressMsgBodyOverHowmuch = 1024 * 4;
    private int retryTimesWhenSendFailed = 2;
    private int retryTimesWhenSendAsyncFailed = 2;
    private boolean retryAnotherBrokerWhenNotStoreOK = false;
    private int maxMessageSize = 1024 * 1024 * 4;
    ...
    @Override
    public SendResult send(Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        return this.defaultMQProducerImpl.send(msg);
    }
    @Override
    public SendResult send(Message msg, long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        return this.defaultMQProducerImpl.send(msg, timeout);
    }
    @Override
    public void send(Message msg, SendCallback sendCallback) throws MQClientException, RemotingException, InterruptedException {
        this.defaultMQProducerImpl.send(msg, sendCallback);
    }
    @Override
    public SendResult send(Message msg, MessageQueue mq, long timeout)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        return this.defaultMQProducerImpl.send(msg, mq, timeout);
    }
    @Override
    public void send(Message msg, MessageQueue mq, SendCallback sendCallback, long timeout)
        throws MQClientException, RemotingException, InterruptedException {
        this.defaultMQProducerImpl.send(msg, mq, sendCallback, timeout);
    }
    @Override
    public void send(Message msg, MessageQueueSelector selector, Object arg, SendCallback sendCallback, long timeout)
        throws MQClientException, RemotingException, InterruptedException {
        this.defaultMQProducerImpl.send(msg, selector, arg, sendCallback, timeout);
    }

    @Override
    public void sendOneway(Message msg, MessageQueueSelector selector, Object arg)
        throws MQClientException, RemotingException, InterruptedException {
        this.defaultMQProducerImpl.sendOneway(msg, selector, arg);
    }
    @Override
    public TransactionSendResult sendMessageInTransaction(Message msg, LocalTransactionExecuter tranExecuter, final Object arg)
        throws MQClientException {
        throw new RuntimeException("sendMessageInTransaction not implement, please use TransactionMQProducer class");
    }
    ...
}

二、消息投遞的具體實現過程

1.、同樣的方式,消息的具體投遞是委託給DefaultMQProducer內部的DefaultMQProducerImpl

private SendResult sendDefaultImpl( Message msg, final CommunicationMode communicationMode, 
         final SendCallback sendCallback, final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    // 校驗 Producer 處於運行狀態
    this.makeSureStateOK();
    // 校驗消息格式
    Validators.checkMessage(msg, this.defaultMQProducer);
    final long invokeID = random.nextLong(); // 調用編號;用於下面打印日誌,標記爲同一次發送消息
    long beginTimestampFirst = System.currentTimeMillis();
    long beginTimestampPrev = beginTimestampFirst;
    @SuppressWarnings("UnusedAssignment")
    long endTimestamp = beginTimestampFirst;
    // 獲取 Topic路由信息
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        MessageQueue mq = null; // 最後選擇消息要發送到的隊列
        Exception exception = null;
        SendResult sendResult = null; // 最後一次發送結果
        int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1; // 同步多次調用
        int times = 0; // 第幾次發送
        String[] brokersSent = new String[timesTotal]; // 存儲每次發送消息選擇的broker名
        // 循環調用發送消息,直到成功
        for (; times < timesTotal; times++) {
            String lastBrokerName = null == mq ? null : mq.getBrokerName();
            @SuppressWarnings("SpellCheckingInspection")
            MessageQueue tmpmq = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName); // 選擇消息要發送到的隊列
            if (tmpmq != null) {
                mq = tmpmq;
                brokersSent[times] = mq.getBrokerName();
                try {
                   beginTimestampPrev = System.currentTimeMillis();
                   // 調用發送消息核心方法
                   sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);
                   endTimestamp = System.currentTimeMillis();
                   // 更新Broker可用性信息
                   this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                   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 (RemotingException e) { // 打印異常,更新Broker可用性信息,更新繼續循環
                        endTimestamp = System.currentTimeMillis();
                        this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                        log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                        log.warn(msg.toString());
                        exception = e;
                        continue;
                    } catch (MQClientException e) { // 打印異常,更新Broker可用性信息,繼續循環
                        endTimestamp = System.currentTimeMillis();
                        this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                        log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                        log.warn(msg.toString());
                        exception = e;
                        continue;
                    } catch (MQBrokerException e) { // 打印異常,更新Broker可用性信息,部分情況下的異常,直接返回,結束循環
                        endTimestamp = System.currentTimeMillis();
                        this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
                        log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                        log.warn(msg.toString());
                        exception = e;
                        switch (e.getResponseCode()) {
                            // 如下異常continue,進行發送消息重試
                            case ResponseCode.TOPIC_NOT_EXIST:
                            case ResponseCode.SERVICE_NOT_AVAILABLE:
                            case ResponseCode.SYSTEM_ERROR:
                            case ResponseCode.NO_PERMISSION:
                            case ResponseCode.NO_BUYER_ID:
                            case ResponseCode.NOT_IN_CURRENT_UNIT:
                                continue;
                            // 如果有發送結果,進行返回,否則,拋出異常;
                            default:
                                if (sendResult != null) {
                                    return sendResult;
                                }
                                throw e;
                        }
                    } catch (InterruptedException e) {
                        endTimestamp = System.currentTimeMillis();
                        this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
                        log.warn(String.format("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
                        log.warn(msg.toString());
                        throw e;
                    }
                } else {
                    break;
                }
            }
            // 返回發送結果
            if (sendResult != null) {
                return sendResult;
            }
            // 根據不同情況,拋出不同的異常
            String info = String.format("Send [%d] times, still failed, cost [%d]ms, Topic: %s, BrokersSent: %s", times, System.currentTimeMillis() - beginTimestampFirst,
                    msg.getTopic(), Arrays.toString(brokersSent)) + FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED);
            MQClientException mqClientException = new MQClientException(info, exception);
            if (exception instanceof MQBrokerException) {
                mqClientException.setResponseCode(((MQBrokerException) exception).getResponseCode());
            } else if (exception instanceof RemotingConnectException) {
                mqClientException.setResponseCode(ClientErrorCode.CONNECT_BROKER_EXCEPTION);
            } else if (exception instanceof RemotingTimeoutException) {
                mqClientException.setResponseCode(ClientErrorCode.ACCESS_BROKER_TIMEOUT);
            } else if (exception instanceof MQClientException) {
                mqClientException.setResponseCode(ClientErrorCode.BROKER_NOT_EXIST_EXCEPTION);
            }
            throw mqClientException;
        }
        // Namesrv找不到異常
        List<String> nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList();
        if (null == nsList || nsList.isEmpty()) {
            throw new MQClientException(
                "No name server address, please set it." + FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL), null).setResponseCode(ClientErrorCode.NO_NAME_SERVER_EXCEPTION);
        }
        // 消息路由找不到異常
        throw new MQClientException("No route info of this topic, " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
            null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION);
    }
                

2、關鍵函數獲取topic的路由信息tryToFindTopicPublishInfo,因爲在創建topic時,會將topic和broker的信息同步到NameSrv,所以這裏可以從NameSrv獲取發送消息的topic理由信息。

private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
   // 緩存中獲取 Topic發佈信息,在start的時候,只是放入了一個空的topicPublicInfo
   TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
   // 當無可用的 Topic發佈信息時,從Namesrv獲取一次
   if (null == topicPublishInfo || !topicPublishInfo.ok()) {
       this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
       this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
       topicPublishInfo = this.topicPublishInfoTable.get(topic);
   }
   // 若獲取的 Topic發佈信息時候可用,則返回
   if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
       return topicPublishInfo;
   } else { // 使用 {@link DefaultMQProducer#createTopicKey} 對應的 Topic發佈信息。用於 Topic發佈信息不存在 && Broker支持自動創建Topic
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
   }
}

接着看更新topic的路由信息

public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault, DefaultMQProducer defaultMQProducer) {
    try {
        if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
           try {
               TopicRouteData topicRouteData;
               if (isDefault && defaultMQProducer != null) { 
                // 使用默認TopicKey獲取TopicRouteData。
                // 當broker開啓自動創建topic開關時,會使用MixAll.DEFAULT_TOPIC進行創建。
                // 當producer的createTopic爲MixAll.DEFAULT_TOPIC時,則可以獲得TopicRouteData。
               // 目的:用於新的topic,發送消息時,未創建路由信息,先使用createTopic的路由信息,等到發送到broker時,進行自動創建。
                                                                  
               topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),1000 * 3);
              if (topicRouteData != null) {
                  for (QueueData data : topicRouteData.getQueueDatas()) {
                      int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());
                      data.setReadQueueNums(queueNums);
                      data.setWriteQueueNums(queueNums);
                   }
               }
               } else {
                   topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);
               }
               if (topicRouteData != null) {
                    TopicRouteData old = this.topicRouteTable.get(topic);
                    boolean changed = topicRouteDataIsChange(old, topicRouteData);
                    if (!changed) {
                        changed = this.isNeedUpdateTopicRouteInfo(topic);
                    } else {
                       log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData);
                    }
                    if (changed) {
                      TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData(); // 克隆對象的原因:topicRouteData會被設置到下面的publishInfo/subscribeInfo

                       // 更新 Broker 地址相關信息
                       for (BrokerData bd : topicRouteData.getBrokerDatas()) {
                           this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
                       }
                       // 更新發布topic的消息,這裏可能涉及到創建MessageQueue
                       {
                          TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData);
                          publishInfo.setHaveTopicRouterInfo(true);
                          for (Entry<String, MQProducerInner> entry : this.producerTable.entrySet()) {
                             MQProducerInner impl = entry.getValue();
                             if (impl != null) {
                                impl.updateTopicPublishInfo(topic, publishInfo);
                             }
                           }
                       }
                       // 這裏會更新一次consumer的信息
                       {
                          Set<MessageQueue> subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData);
                          for (Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
                             MQConsumerInner impl = entry.getValue();
                             if (impl != null) {
                                 impl.updateTopicSubscribeInfo(topic, subscribeInfo);
                             }
                           }
                        }
                        log.info("topicRouteTable.put TopicRouteData[{}]", cloneTopicRouteData);
                        this.topicRouteTable.put(topic, cloneTopicRouteData);
                        return true;
                   }
             } else {
                    log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}", topic);
             }
         } catch (Exception e) {
             if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && !topic.equals(MixAll.DEFAULT_TOPIC)) {
                 log.warn("updateTopicRouteInfoFromNameServer Exception", e);
              }
          } finally {
              this.lockNamesrv.unlock();
          }
       } else {
           log.warn("updateTopicRouteInfoFromNameServer tryLock timeout {}ms", LOCK_TIMEOUT_MILLIS);
       }
   } catch (InterruptedException e) {
       log.warn("updateTopicRouteInfoFromNameServer Exception", e);
   }

   return false;
}

3、因爲一個topic可能有很多的消息隊列MessageQueue,如何選取MessageQueue路由呢?這裏會通過一種容錯機制選取出最優的MessageQueue。默認的MQFaultStrategy實現

// MQ 故障策略
public class MQFaultStrategy {
    /**
     * 延遲故障容錯,維護每個Broker的發送消息的延遲
     * key:brokerName
     */
    private final LatencyFaultTolerance<String> latencyFaultTolerance = new LatencyFaultToleranceImpl();
    /**
     * 發送消息延遲容錯開關
     */
    private boolean sendLatencyFaultEnable = false;
    /**
     * 延遲級別數組
     */
    private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
    /**
     * 不可用時長數組
     */
    private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};  
    public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
        if (this.sendLatencyFaultEnable) {
            try {
                // 感覺是在MessageQueue中輪流選取
                int index = tpInfo.getSendWhichQueue().getAndIncrement();
                for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
                    int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
                    if (pos < 0)
                        pos = 0;
                    MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
                    if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
                        if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
                            return mq;
                    }
                }
                // 選擇一個相對好的broker,並獲得其對應的一個消息隊列,不考慮該隊列的可用性
                final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
                int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker);
                if (writeQueueNums > 0) {
                    final MessageQueue mq = tpInfo.selectOneMessageQueue();
                    if (notBestBroker != null) {
                        mq.setBrokerName(notBestBroker);
                        mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);
                    }
                    return mq;
                } else {
                    latencyFaultTolerance.remove(notBestBroker);
                }
            } catch (Exception e) {
                log.error("Error occurred when selecting message queue", e);
            }
            // 選擇一個消息隊列,不考慮隊列的可用性
            return tpInfo.selectOneMessageQueue();
        }
        // 獲得 lastBrokerName 對應的一個消息隊列,不考慮該隊列的可用性
        return tpInfo.selectOneMessageQueue(lastBrokerName);
    }
}

4、發送消息的核心:非常主要一點,因爲MessageQueue可能在不同的broker上,每一個brokerName可能有一組broker,所以這個時候會向這組broker的主broker投遞消息。

//發送消息的實際操作者
private SendResult sendKernelImpl(final Message msg,
                                  final MessageQueue mq,
                                  final CommunicationMode communicationMode,
                                  final SendCallback sendCallback,
                                  final TopicPublishInfo topicPublishInfo,
                                  final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    long beginStartTime = System.currentTimeMillis();
     //獲得需要發送的broker地址,主要是發送給master
    String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
    if (null == brokerAddr) {
        //重新拉取topic信息
        tryToFindTopicPublishInfo(mq.getTopic());
        brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
    }
    SendMessageContext context = null;
    if (brokerAddr != null) {
        //是否是vip的操作,主要是端口-2的操作,然後重新拼接addr
        brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);
        //發送的消息體
        byte[] prevBody = msg.getBody();
        try {
            //for MessageBatch,ID has been set in the generating process
            //是否是批量消息體
            if (!(msg instanceof MessageBatch)) {
                MessageClientIDSetter.setUniqID(msg);
            }

            int sysFlag = 0;
            boolean msgBodyCompressed = false;
            //壓縮消息體
            if (this.tryToCompressMessage(msg)) {
                sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
                msgBodyCompressed = true;
            }

            //判斷是否是事務消息,設置事務屬性
            final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
            if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
                sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
            }

            //驗證自定義的hook
            if (hasCheckForbiddenHook()) {
                CheckForbiddenContext checkForbiddenContext = new CheckForbiddenContext();
                checkForbiddenContext.setNameSrvAddr(this.defaultMQProducer.getNamesrvAddr());
                checkForbiddenContext.setGroup(this.defaultMQProducer.getProducerGroup());
                checkForbiddenContext.setCommunicationMode(communicationMode);
                checkForbiddenContext.setBrokerAddr(brokerAddr);
                checkForbiddenContext.setMessage(msg);
                checkForbiddenContext.setMq(mq);
                checkForbiddenContext.setUnitMode(this.isUnitMode());
                this.executeCheckForbiddenHook(checkForbiddenContext);
            }

            //驗證發送消息的hook
            if (this.hasSendMessageHook()) {
                context = new SendMessageContext();
                context.setProducer(this);
                context.setProducerGroup(this.defaultMQProducer.getProducerGroup());
                context.setCommunicationMode(communicationMode);
                context.setBornHost(this.defaultMQProducer.getClientIP());
                context.setBrokerAddr(brokerAddr);
                context.setMessage(msg);
                context.setMq(mq);
                String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (isTrans != null && isTrans.equals("true")) {
                    context.setMsgType(MessageType.Trans_Msg_Half);
                }

                if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
                    context.setMsgType(MessageType.Delay_Msg);
                }
                this.executeSendMessageHookBefore(context);
            }

            //消息頭的封裝
            SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
            //消息發送組
            requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
            //消息的topic
            requestHeader.setTopic(msg.getTopic());
            //消息的默認topic
            requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
            //消息的默認queue數量
           requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
            //消息發送的queueid
            requestHeader.setQueueId(mq.getQueueId());
            //特殊標識
            requestHeader.setSysFlag(sysFlag);
            //消息創建的時間戳
            requestHeader.setBornTimestamp(System.currentTimeMillis());
            //標識
            requestHeader.setFlag(msg.getFlag());
            //消息的擴展屬性
            requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
            //消息的消費
            requestHeader.setReconsumeTimes(0);
            //模型
            requestHeader.setUnitMode(this.isUnitMode());
            //批量
            requestHeader.setBatch(msg instanceof MessageBatch);
            //特殊標記的處理
            if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
                if (reconsumeTimes != null) {
                    requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));
                    MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);
                }

                String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg);
                if (maxReconsumeTimes != null) {
                    requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));
                    MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);
                }
            }

            SendResult sendResult = null;
            //消息發送的模型
            switch (communicationMode) {
                //異步發送
                case ASYNC:
                    Message tmpMessage = msg;
                    if (msgBodyCompressed) {
                        //If msg body was compressed, msgbody should be reset using prevBody.
                        //Clone new message using commpressed message body and recover origin massage.
                        //Fix bug:https://github.com/apache/rocketmq-externals/issues/66
                        tmpMessage = MessageAccessor.cloneMessage(msg);
                        msg.setBody(prevBody);
                    }
                    long costTimeAsync = System.currentTimeMillis() - beginStartTime;
                    if (timeout < costTimeAsync) {
                        throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                    }
                    //參數區別主要是回調相關的配置
                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                        brokerAddr,
                        mq.getBrokerName(),
                        tmpMessage,
                        requestHeader,
                        timeout - costTimeAsync,
                        communicationMode,
                        sendCallback,
                        topicPublishInfo,
                        this.mQClientFactory,
                        this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
                        context,
                        this);
                    break;
                //同步及單發
                case ONEWAY:
                case SYNC:
                    long costTimeSync = System.currentTimeMillis() - beginStartTime;
                    if (timeout < costTimeSync) {
                        throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                    }
                    //消息的最終發送
                    sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                        brokerAddr,
                        mq.getBrokerName(),
                        msg,
                        requestHeader,
                        timeout - costTimeSync,
                        communicationMode,
                        context,
                        this);
                    break;
                default:
                    assert false;
                    break;
            }

            //是否有發送消息hook
            if (this.hasSendMessageHook()) {
                context.setSendResult(sendResult);
                this.executeSendMessageHookAfter(context);
            }

            return sendResult;
        } catch (RemotingException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } catch (MQBrokerException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } catch (InterruptedException e) {
            if (this.hasSendMessageHook()) {
                context.setException(e);
                this.executeSendMessageHookAfter(context);
            }
            throw e;
        } finally {
            msg.setBody(prevBody);
        }
    }

    throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}

6、組裝netty的發送指令,開始消息的最終發送:

//發送消息的核心操作實現
public SendResult sendMessage(
    final String addr,//地址
    final String brokerName,//broker名稱
    final Message msg,//消息
    final SendMessageRequestHeader requestHeader,//消息頭
    final long timeoutMillis,//超時
    final CommunicationMode communicationMode,//發送模式
    final SendCallback sendCallback,//發送後的回調
    final TopicPublishInfo topicPublishInfo,//topic的配置
    final MQClientInstance instance,//基於client的發送實例
    final int retryTimesWhenSendFailed,//重試次數
    final SendMessageContext context,//發送消息的上下文信息
    final DefaultMQProducerImpl producer//發送消息者
) throws RemotingException, MQBrokerException, InterruptedException {
    long beginStartTime = System.currentTimeMillis();
    RemotingCommand request = null;
    //協議轉換
    if (sendSmartMsg || msg instanceof MessageBatch) {
        SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
        request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
    } else {
        request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
    }

    request.setBody(msg.getBody());

    switch (communicationMode) {
        case ONEWAY:
            this.remotingClient.invokeOneway(addr, request, timeoutMillis);
            return null;
        case ASYNC:
            final AtomicInteger times = new AtomicInteger();
            long costTimeAsync = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTimeAsync) {
                throw new RemotingTooMuchRequestException("sendMessage call timeout");
            }
            this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance,
                retryTimesWhenSendFailed, times, context, producer);
            return null;
        case SYNC:
            long costTimeSync = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTimeSync) {
                throw new RemotingTooMuchRequestException("sendMessage call timeout");
            }
            return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request);
        default:
            assert false;
            break;
    }

    return null;
}

// 同步調用發送方法
private SendResult sendMessageSync(
    final String addr,
    final String brokerName,
    final Message msg,
    final long timeoutMillis,
    final RemotingCommand request
) throws RemotingException, MQBrokerException, InterruptedException {
    //同步發送消息
    RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
    assert response != null;

    //處理請求的相應結果
    return this.processSendResponse(brokerName, msg, response);
}

//網絡通信的核心方法實現,同步
@Override
public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
    throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
    long beginStartTime = System.currentTimeMillis();

    //獲得通信的channel
    final Channel channel = this.getAndCreateChannel(addr);

    //驗證channel的有效性
    if (channel != null && channel.isActive()) {
        try {
            //前置處理
            doBeforeRpcHooks(addr, request);

            long costTime = System.currentTimeMillis() - beginStartTime;
            if (timeoutMillis < costTime) {
                throw new RemotingTimeoutException("invokeSync call timeout");
            }

            //執行方法調用
            RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);

            //後置處理
            doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
            return response;
        } catch (RemotingSendRequestException e) {
            log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
            this.closeChannel(addr, channel);
            throw e;
        } catch (RemotingTimeoutException e) {
            if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
                this.closeChannel(addr, channel);
                log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
            }
            log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
            throw e;
        }
    } else {
        //如果異常則刪除執行的channel的操作
        this.closeChannel(addr, channel);
        throw new RemotingConnectException(addr);
    }
}

//同步操作的核心
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
    final long timeoutMillis)
    throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
    final int opaque = request.getOpaque();

    try {
        //異步調用封裝,處理netty通信後的結果
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);

        //結果的併發處理及通知,及後續的異常處理收集
        this.responseTable.put(opaque, responseFuture);
        final SocketAddress addr = channel.remoteAddress();
        //netty的消息發送,只負責發送
        channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture f) throws Exception {
                //操作成功後馬上返回
                if (f.isSuccess()) {
                    responseFuture.setSendRequestOK(true);
                    return;
                } else {
                    responseFuture.setSendRequestOK(false);
                }
                //同步處理後刪除對應的請求對象
                responseTable.remove(opaque);
                responseFuture.setCause(f.cause());
                responseFuture.putResponse(null);
                log.warn("send a request command to channel <" + addr + "> failed.");
            }
        });

        //相應結果的處理,實際的操作在NettyClientHandler中實現,根據opaque來更新結果
        RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
        if (null == responseCommand) {
            if (responseFuture.isSendRequestOK()) {
                throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                    responseFuture.getCause());
            } else {
                throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
            }
        }

        return responseCommand;
    } finally {
        this.responseTable.remove(opaque);
    }
}

如果消息是異步發送機制,會通過sendCallBack進行回調處理。

7、broker接收到消息發送的指令後,會做消息的持久化處理,後面在broker環節將重要介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章