RocketMQ-Producer源码解析

RocketMQ-Producer

为什么学习RocketMQ?很大一部分原因是被官方文档里的这句洗脑了:Apache RocketMQ is a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability.注意:是trillion-level。下面解析的主要是RocketMQ中Producer发送同步消息的部分源码(代码部分加上注释后直接从编译器里copy出来的格式多少有些不友好,望大家见谅) 

注:转载请注明出处

RocketMQ-Producer发送同步消息流程:消息校验-获取Topic信息-进入发送消息循环-选择发送队列-消息发送(包含很多具体步骤)-检测发送状态-更新Broker延时信息-返回消息发送结果

  • 用到的主要方法:
    • DefaultMQProducer-send(msg)
    • DefaultMQProducerImpl-sendDefaultImpl(msg,communicationMode,sendCallback,timeout)
    • DefaultMQProducerImpl-tryToFindTopicPublishInfo(topic)
    • MQFaultStrategy-selectOneMessageQueue(tpInfo,lastBrokerName)
    • DefaultMQProducerImpl-sendKernelImpl(msg,mq,communicationMode,sendCallback,topicPublishInfo,timeout)
    • MQFaultStrategy-updateFaultItem(brokerName,currentLatency,isolation)

具体解析:

  1. 首先从业务代码里调用RocketMQ的send方法,发送普通消息的话会调用到下面这里可以看出此时的消息发送是同步发送
  2. 之后进入到DefaultMQProducerImpl-sendDefaultImpl(msg,communicationMode,sendCallback,timeout)这个方法进行发送消息,具体代码解析如下
    private SendResult sendDefaultImpl(
            Message msg,// 封装好的消息,包括topic,tag,消息内容等信息
            final CommunicationMode communicationMode,// 封装后传进来的是SYNC同步发送
            final SendCallback sendCallback,// null
            final long timeout // 默认3000ms
        ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
            this.makeSureStateOK();// 检测Producer状态是否为RUNNING
            // 校验消息是否符合规范(校验topic和body各种属性 如是否为空,消息体大小是否过大(最大4M))
            Validators.checkMessage(msg, this.defaultMQProducer);
            
            final long invokeID = random.nextLong();
            long beginTimestampFirst = System.currentTimeMillis();
            long beginTimestampPrev = beginTimestampFirst;
            long endTimestamp = beginTimestampFirst;
            // 获取Topic信息,详见tryToFindTopicPublishInfo
            TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
            if (topicPublishInfo != null && topicPublishInfo.ok()) {
                MessageQueue mq = null;
                Exception exception = null;
                SendResult sendResult = null;
                // 目测是个三元表达式,既然是同步发送那么timesTotal = 3
                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();// 记录每次消息发送到哪个Broker
                        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()) {
                                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) {// 更新Broker延时信息,打印出异常结束循环
                            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());
                            log.warn("sendKernelImpl exception", 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 (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;
            }
            // NameServer异常
            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);
            }
         // Topic路由信息异常
            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);
        }
  3. 其中调用到DefaultMQProducerImpl-tryToFindTopicPublishInfo(topic)方法获取Topic信息:
    private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
        	// 从缓存中获取Topic信息,是个ConcurrentMap
            TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
            // 如果没获取到,则去NameServer获取
            if (null == topicPublishInfo || !topicPublishInfo.ok()) {
                this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
                // 从NameServer取Topic信息之后会有介绍(此处调用经过封装,后两个参数设为false和null)
                this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
                topicPublishInfo = this.topicPublishInfoTable.get(topic);
            }
            // 检测Topic信息是否可用,可用则返回
            if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
                return topicPublishInfo;
            } else {
            	// 从NameServer取Topic信息,之后会有介绍(此处调用未经封装)
                this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
                topicPublishInfo = this.topicPublishInfoTable.get(topic);
                return topicPublishInfo;
            }
        }
  4. 之后调用MQFaultStrategy-selectOneMessageQueue(tpInfo,lastBrokerName)方法获取到要把消息发送到哪个队列:
    public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
        	// 默认情况下为false
            if (this.sendLatencyFaultEnable) {
                try {
                	// 如果是第一次发送,lastBrokerName为空,则选择一个可用的Broker中的队列返回
                	// 如果不是第一次发送,最好的情况是在BrokerName为lastBrokerName且可用的Broker中选择一个队列返回
                    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);
                        // 可用的Broker通过一个ConcurrentHashMap维护,key为BrokerName
                        if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
                            if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))
                                return mq;
                        }
                    }
                    // 其次从FaultItemTable选择一个未验证可用性的队列
                    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();
            }
            // 未开启容错策略
            // 第一次发送的话随机选择Broker,重试的时候选择BrokerName != lastBrokerName的Broker中的队列,未验证队列的可用性
            return tpInfo.selectOneMessageQueue(lastBrokerName);
        }
    未开启容错策略的话直接走最下面return这行代码,开启容错策略的话会有三种情况,最好的情况是选择可用的Broker中的队列
  5. DefaultMQProducerImpl-sendKernelImpl(msg,mq,communicationMode,sendCallback,topicPublishInfo,timeout)是发送消息的核心方法,同步发送的话包含消息校验-消息发送-校验发送结果流程,代码比较多
    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 {
            	// 从BrokerAddrTable中获取Broker地址
                String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
                if (null == brokerAddr) {
                	// tryToFindTopicPublishInfo这个方法有解析
                    tryToFindTopicPublishInfo(mq.getTopic());
                    brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
                }
    
                SendMessageContext context = null;
                if (brokerAddr != null) {
                	// 检测Broker是否使用vip,使用的话返回新的brokerAddr
                    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;
                        // 消息压缩,把压缩后的消息存在msg的body里,修改sysFlag
                        if (this.tryToCompressMessage(msg)) {
                            sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
                        }
                        //如果属于事物消息,修改sysFlag
                        final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                        if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
                            sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
                        }
                        // 是否注册了CheckForbiddenHook,若注册了则检查消息否属于禁止消息
                        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);
                        }
                        // 是否注册了sendMessageHookList
                        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对象
                        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:
                                sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                                    brokerAddr,
                                    mq.getBrokerName(),
                                    msg,
                                    requestHeader,
                                    timeout,
                                    communicationMode,
                                    sendCallback,
                                    topicPublishInfo,
                                    this.mQClientFactory,
                                    this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
                                    context,
                                    this);
                                break;
                            case ONEWAY:
                            case SYNC:
                            	// 同步发送消息
                            	// 消息发送完之后会调用MQClientAPIImpl.processSendResponse方法检测发送结果并返回到这里,之后会有详细解析
                                sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                                    brokerAddr,
                                    mq.getBrokerName(),
                                    msg,
                                    requestHeader,
                                    timeout,
                                    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);
                    }
                }
                // 抛出Broker不存在异常
                throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
            }
  6. 发送完消息之后还会有一些操作,比如更新Broker延时信息
    public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
        	// 传进来的isolation为false,所以根据currentLatency和latencyMax判断延时情况
            if (this.sendLatencyFaultEnable) {
                // 更新FaultItemTable,当然了sendLatencyFaultEnable目测为false
                long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
                this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
            }
        }
  7. 返回发送结果SendResult

猜你喜欢

转载自blog.csdn.net/yuu1009/article/details/80658541