rocketmq发送消息流程

118 阅读4分钟

rocketmq发送消息流程

消息发送入口

默认实现类是org.apache.rocketmq.client.producer.DefaultMQProducer。 我们随机找一个发送消息的方法

public SendResult send(
    Message msg) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    msg.setTopic(withNamespace(msg.getTopic()));
    return this.defaultMQProducerImpl.send(msg);
}

点下去就是org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl。这是一个实现类。 最后我们看到调用的方式

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;
    TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
    if (topicPublishInfo != null && topicPublishInfo.ok()) {
        boolean callTimeout = false;
        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];
        for (; times < timesTotal; times++) {
            String lastBrokerName = null == mq ? null : mq.getBrokerName();
            MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
            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;
                    if (timeout < costTime) {
                        callTimeout = true;
                        break;
                    }

                    sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
                    endTimestamp = System.currentTimeMillis();
                    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) {
                    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) {
                    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) {
                    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;
                    if (this.defaultMQProducer.getRetryResponseCodes().contains(e.getResponseCode())) {
                        continue;
                    } else {
                        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));

        info += FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED);

        MQClientException mqClientException = new MQClientException(info, exception);
        if (callTimeout) {
            throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout");
        }

        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;
    }

    validateNameServerSetting();

    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);
}

接下来的流程是这样的

消息长度验证

方法Validators.checkMessage(msg, this.defaultMQProducer);
首先确保生产者处于运行状态,然后验证消息是否符合相应的规范,具体的规范要求是主题名称、消息体不能为空、消息长度不能等于0且默认不能超过允许发送消息的最大长度4M(maxMessageSize=l02410244)。

查找主题路由信息

通过tryToFindTopicPublishInfo方法去找topic的路由相关信息。如果生产者中缓存了topic的路由信息,如果该路由信息中包含了消息队列,则直接返回该路由信息,如果没有缓存或没有包含消息队列,则向NameServer查询该topic的路由信息

private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
    TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
    if (null == topicPublishInfo || !topicPublishInfo.ok()) {
        this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
    }

    if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
        return topicPublishInfo;
    } else {
        this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
        topicPublishInfo = this.topicPublishInfoTable.get(topic);
        return topicPublishInfo;
    }
}

接下来查看updateTopicRouteInfoFromNameServer方法

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) {
                    topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),
                        clientConfig.getMqClientApiTimeout());
                    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, clientConfig.getMqClientApiTimeout());
                }
                if (topicRouteData != null) {
                    TopicRouteData old = this.topicRouteTable.get(topic);
                    boolean changed = topicRouteData.topicRouteDataChanged(old);
                    if (!changed) {
                        changed = this.isNeedUpdateTopicRouteInfo(topic);
                    } else {
                        log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData);
                    }

                    if (changed) {

                        for (BrokerData bd : topicRouteData.getBrokerDatas()) {
                            this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs());
                        }

                        // Update endpoint map
                        {
                            ConcurrentMap<MessageQueue, String> mqEndPoints = topicRouteData2EndpointsForStaticTopic(topic, topicRouteData);
                            if (!mqEndPoints.isEmpty()) {
                                topicEndPointsTable.put(topic, mqEndPoints);
                            }
                        }

                        // Update Pub info
                        {
                            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);
                                }
                            }
                        }

                        // Update sub info
                        if (!consumerTable.isEmpty()) {
                            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);
                                }
                            }
                        }
                        TopicRouteData cloneTopicRouteData = new TopicRouteData(topicRouteData);
                        log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData);
                        this.topicRouteTable.put(topic, cloneTopicRouteData);
                        return true;
                    }
                } else {
                    log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}. [{}]", topic, this.clientId);
                }
            } catch (MQClientException e) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && !topic.equals(TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC)) {
                    log.warn("updateTopicRouteInfoFromNameServer Exception", e);
                }
            } catch (RemotingException e) {
                log.error("updateTopicRouteInfoFromNameServer Exception", e);
                throw new IllegalStateException(e);
            } finally {
                this.lockNamesrv.unlock();
            }
        } else {
            log.warn("updateTopicRouteInfoFromNameServer tryLock timeout {}ms. [{}]", LOCK_TIMEOUT_MILLIS, this.clientId);
        }
    } catch (InterruptedException e) {
        log.warn("updateTopicRouteInfoFromNameServer Exception", e);
    }

    return false;
}
  1. 如果isDefault为true,则使用默认主题去查询,如果查询到路由信息,则替换路由信息中读写队列个数为消息生产者默认的队列个数(defaultTopicQueueNums);如果isDefault为false,则使用参数topic去查询;如果未查询到路由信息,则返回false,表示路由信息未变化
  2. 如果路由信息找到,与本地缓存中的路由信息进行对比,判断路由信息是否发生了改变, 如果未发生变化,则直接返回 false
  3. 更新MQClientlnstanceBroker地址缓存表
  4. 根据 topicRouteData 中的 List 转换成问icPublisInfo的 List列表。 其具体实现在topicRouteData2TopicPublishInfo, 然后会更新该 MQClientInstance的所有消息发送关于 topic 的路由信息.

选择消息队列

首先需要看配置有没有配置故障延迟机会
sendLatencyFaultEnable=false ,默认不启用 Broker 故障延迟机制 。
sendLatencyFaultEnable=true ,启用 Broker 故障延迟机制 。 接下来看方法selectOneMessageQueue

public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
 if (this.sendLatencyFaultEnable) {
     try {
         int index = tpInfo.getSendWhichQueue().incrementAndGet();
         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()))
                 return mq;
         }

         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().incrementAndGet() % writeQueueNums);
             }
             return mq;
         } else {
             latencyFaultTolerance.remove(notBestBroker);
         }
     } catch (Exception e) {
         log.error("Error occurred when selecting message queue", e);
     }

     return tpInfo.selectOneMessageQueue();
 }

 return tpInfo.selectOneMessageQueue(lastBrokerName);
}
  • 根据对消息队列进行轮询获取一个消息队列。
  • 验证该消息队列是否可用,latencyFaultTolerance.isAvailable(mq.getBrokerName()) 是关键。
  • 如果返回的MessageQueue可用,移除latencyFaultTolerance关于该topic条目,表 明该Broker故障已经恢复。

消息发送

方法:DefaultMQProducerimpl#sendKernelImpl

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();
 String brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq);
 String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName);
 if (null == brokerAddr) {
     tryToFindTopicPublishInfo(mq.getTopic());
     brokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(mq);
     brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(brokerName);
 }

 SendMessageContext context = null;
 if (brokerAddr != null) {
     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);
         }

         boolean topicWithNamespace = false;
         if (null != this.mQClientFactory.getClientConfig().getNamespace()) {
             msg.setInstanceId(this.mQClientFactory.getClientConfig().getNamespace());
             topicWithNamespace = true;
         }

         int sysFlag = 0;
         boolean msgBodyCompressed = false;
         if (this.tryToCompressMessage(msg)) {
             sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
             sysFlag |= compressType.getCompressionFlag();
             msgBodyCompressed = true;
         }

         final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
         if (Boolean.parseBoolean(tranMsg)) {
             sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
         }

         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);
         }

         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);
             context.setNamespace(this.defaultMQProducer.getNamespace());
             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());
         requestHeader.setTopic(msg.getTopic());
         requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
         requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
         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;
                 boolean messageCloned = false;
                 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);
                     messageCloned = true;
                     msg.setBody(prevBody);
                 }

                 if (topicWithNamespace) {
                     if (!messageCloned) {
                         tmpMessage = MessageAccessor.cloneMessage(msg);
                         messageCloned = true;
                     }
                     msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
                 }

                 long costTimeAsync = System.currentTimeMillis() - beginStartTime;
                 if (timeout < costTimeAsync) {
                     throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
                 }
                 sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                     brokerAddr,
                     brokerName,
                     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,
                     brokerName,
                     msg,
                     requestHeader,
                     timeout - costTimeSync,
                     communicationMode,
                     context,
                     this);
                 break;
             default:
                 assert false;
                 break;
         }

         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);
         msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQProducer.getNamespace()));
     }
 }

 throw new MQClientException("The broker[" + brokerName + "] not exist", null);
}
  • 据MessageQueue获取Broker的网络地址。如果MQClientlnstance的brokerAddrTable未缓存该Broker的信息,则从NameServer主动更新一下topic的路由信息。如果路由更新后还是找不到Broker信息,则抛出MQClientException,提示Broker不存在。

  • 为消息分配全局唯一ID,如果消息体默认超过4K(compressMsgBodyOverHowmuch),会对消息体采用zip压缩,并设置消息的系统标记为MessageSysFlag.COMPRESSED_FLAG。如果是事务Prepared消息,则设置消息的系统标记为MessageSysFlag.TRANSACTION_PREPAREDTYPE。

  • 如果注册了消息发送钩子函数, 则执行消息发送之前的增强逻辑 。 通过 DefaultMQProducerlmpl#registerSendMessageHook 注册钩子处理类,并且可以注册多个 。 简单看 一下钩子处理类接口

  • 构建消息发送请求包。主要包含如下重要信息:生产者组、主题名称、默认创建主题Key、该主题在单个Broker默认队列数、队列ID(队列序号)、消息系统标记 (MessageSysFlag)、消息发送时间、消息标记(RocketMQ对消息中的flag不做任何处理, 供应用程序使用)、消息扩展属性、消息重试次数、是否是批量消息等

  • 根据消息发送方式,同步、异步、单向方式进行网络传输。

  • 如果注册了消息发送钩子函数,执行after逻辑。注意,就算消息发送过程中发生RemotingException、MQBrokerException、InterruptedException时该方法也会执行