RocketMq消费者拉取消息源码

1、DefaultMQPushConsumer分发处理消息源码

/**
 * 分发处理消息
 * @param pullRequestList
 */
@Override
public void dispatchPullRequest(List<PullRequest> pullRequestList) {
    
    
    //遍历拉去请求
    for (PullRequest pullRequest : pullRequestList) {
    
    
        //将请求存入PullMessageService服务的pullRequestQueue集合中,后续异步的消费,执行拉取消息的请求
        this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
        log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest);
    }
}

2、pullMessageServer

Push模式下, 消息拉取由PullMessageService服务实现,PullMessageService继承了ServiceThread,因此他也是一个异步线程任务。

在它的run方法中,在循环中不断的从pullRequestQueue中阻塞式的获取并移除队列的头部数据,即拉取消息的请求,然后调用pullMessage方法根据该请求去broker拉取消息

 /**
     * PullMessageService的方法
     * 从MQClientInstance的启动流程中可以看出,RocketMQ使用一个单独的线程PullMessageService来负责消息的拉取。
     */
    @Override
    public void run() {
    
    
        logger.info(this.getServiceName() + " service started");
 
        //循环拉取消息
        //运行时逻辑。如果服务没有停止,则在死循环中执行拉取消息的操作。
        while (!this.isStopped()) {
    
    
            try {
    
    
                /**
                 * TODO: 从请求队列中获取拉取消息请求,刚开始肯定获取不到(因为没有经过重平衡分配数据),那么我们就要看是什么时候将拉取请求放入队列中的呢?
                 * 没错,这就要看重平衡服务了,这里最开始是阻塞的,经过重平衡可以获取到PullRequest
                 */
                MessageRequest messageRequest = this.messageRequestQueue.take();
                /**
                 * 如果消息请求模式是POP
                 * POP:在pop模式下工作的消费者可以共享MessageQueue
                 */
                if (messageRequest.getMessageRequestMode() == MessageRequestMode.POP) {
    
    
                    //执行popMessage
                    this.popMessage((PopRequest)messageRequest);
                } else {
    
    
                    //TODO: 拉取消息
                    this.pullMessage((PullRequest)messageRequest);
                }
            } catch (InterruptedException ignored) {
    
    
            } catch (Exception e) {
    
    
                logger.error("Pull Message Service Run Method exception", e);
            }
        }
 
        logger.info(this.getServiceName() + " service end");
    }

3、pullMessageServer的pullMessage源码分析

pullMessage方法是拉取消息的入口方法。内部实际调用DefaultMQPushConsumerImpl的pullMessage方法。

  /**
     * PullMessageService的方法
     * 拉取消息服务
     * @param pullRequest       拉取请求
     */
    private void pullMessage(final PullRequest pullRequest) {
    
    
        //从consumerTable中获取pullRequest中保存的消费者组的取消费者实例
        final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
        if (consumer != null) {
    
    
            //强转为推送模式消费者
            DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
            //拉取消息
            impl.pullMessage(pullRequest);
        } else {
    
    
            logger.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);
        }
    }

4、DefaultMQPushConsumerImpl的拉取消息

  1. 服务状态校验。如果消费者服务状态异常,或者消费者暂停了,那么延迟发送拉取消息请求。

  2. 流控校验。默认情况下,如果processQueue中已缓存的消息总数量大于设定的阈值,默认1000,或者processQueue中已缓存的消息总大小大于设定的阈值,默认100MB那么同样延迟发送拉取消息请求。

  3. 顺序消费和并发消费的校验。如果是并发消费并且内存中消息的offset的最大跨度大于设定的阈值,默认2000。那么延迟发送拉取消息请求。如果是顺序消费并且没有锁定过,那么需要设置消费点位。

  4. 创建拉取消息的回调函数对象PullCallback,当拉取消息的请求返回之后,将会调用回调函数。这里面的源码我们后面再讲。

  5. 判断是否允许上报消费点位,如果是集群消费模式,并且本地内存有关于此mq的offset,那么设置commitOffsetEnable为true,表示拉取消息时可以上报消费位点给Broker进行持久化。

  6. 调用pullAPIWrapper.pullKernelImpl方法真正的拉取消息。

   /**
     * DefaultMQPushConsumerImpl的方法
     * 拉取消息
     * @param pullRequest       拉取消息请求
     */
    public void pullMessage(final PullRequest pullRequest) {
    
    
        //获取对应的处理队列
        final ProcessQueue processQueue = pullRequest.getProcessQueue();
        //如果该处理队列被丢弃,直接返回(默认是false)
        if (processQueue.isDropped()) {
    
    
            log.info("the pull request[{}] is dropped.", pullRequest.toString());
            return;
        }
 
        //如果处理队列未被丢弃,更新时间戳
        pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());
 
        /**
         * 1. 状态校验
         */
        try {
    
    
            //确保此consumer的服务状态正常,服务状态为RUNNING才为正常,如果服务状态不是RUNNING,那么抛出异常
            this.makeSureStateOK();
        } catch (MQClientException e) {
    
    
            log.warn("pullMessage exception, consumer state not ok", e);
            //延迟3s发送拉取消息请求
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            return;
        }
 
        //如果消费者暂停了,那么延迟1s后再发送拉取消息请求(默认是false)
        if (this.isPause()) {
    
    
            log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup());
            this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND);
            return;
        }
 
        /**
         * 2. 流控校验
         */
        //获得processQueue中已缓存的消息总数量
        long cachedMessageCount = processQueue.getMsgCount().get();
        //获取processQueue中已缓存的消息总大小MB
        long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024);
 
        //TODO: 判断是否出发流控?目的:流控主要是保护消费者。当消费者消费能力不够时,拉取速度太快会导致大量消息积压,很可能导致内存溢出。
        /**
         * TODO:如果消息数量大于配置,则从消息数量进行流控
         * 1. 判断queue缓存的消息数量是否超过1000(可以根据pullThresholdForQueue参数配置)
         *    如果超过了1000,则先不去broker拉取消息,而是先暂停50ms,然后重新将对象放入队列(this.pullRequestQueue.put(pullRequest)),然后重新拉取
         */
        if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) {
    
    
            //延迟执行拉取消息请求
            this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
            if ((queueFlowControlTimes++ % 1000) == 0) {
    
    
                //缓存的消息计数超过了阈值{},因此流控制也超过了阈值
                log.warn(
                    "the cached message count exceeds the threshold {}, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}",
                    this.defaultMQPushConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes);
            }
            return;
        }
 
        /**
         * TODO: 如果消息大小超过配置,则从消息大小进行流控
         * 2. 判断queue缓存的消息大小是否超过100M(可以根据pullThresholdSizeForQueue参数配置)
         *    如果超过100M,则先不去broker拉取消息,而是先暂停50ms,然后重新将对象放入队列中(this.pullRequestQueue.put(pullRequest)),然后重新拉取
         */
        if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) {
    
    
            //延迟执行拉取消息请求
            this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
            if ((queueFlowControlTimes++ % 1000) == 0) {
    
    
                //缓存的消息大小超过了阈值{}MiB,流控制也是如此
                log.warn(
                    "the cached message size exceeds the threshold {} MiB, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}",
                    this.defaultMQPushConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes);
            }
            return;
        }
 
        /**
         * 3 顺序消费和并发消费的校验
         */
        //如果不是顺序消费,即并发消费
        if (!this.consumeOrderly) {
    
    
            //如果内存中消息的offset的最大跨度大于设置的阈值,默认2000
            if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) {
    
    
                //延迟50ms发送拉取消息请求
                this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
                if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) {
    
    
                    log.warn(
                        "the queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, pullRequest={}, flowControlTimes={}",
                        processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(),
                        pullRequest, queueMaxSpanFlowControlTimes);
                }
                return;
            }
        } else {
    
    
            //顺序消费校验,如果已经锁定
            if (processQueue.isLocked()) {
    
    
                //如果此前没有锁定过,那么需要设置消费点位(默认false)
                if (!pullRequest.isPreviouslyLocked()) {
    
    
                    long offset = -1L;
                    try {
    
    
                        //获取该MessageQueue的下一个消息的消费偏移量offset
                        offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue());
                        if (offset < 0) {
    
    
                            throw new MQClientException(ResponseCode.SYSTEM_ERROR, "Unexpected offset " + offset);
                        }
                    } catch (Exception e) {
    
    
                        //延迟3s发送拉取消息请求
                        this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
                        log.error("Failed to compute pull offset, pullResult: {}", pullRequest, e);
                        return;
                    }
                    boolean brokerBusy = offset < pullRequest.getNextOffset();
                    //第一次拉取消息时,请修复来自代理的偏移量。pull请求:
                    log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}",
                        pullRequest, offset, brokerBusy);
                    if (brokerBusy) {
    
    
                        //第一次拉取消息,但拉取请求偏移量大于代理消耗偏移量
                        log.info("[NOTIFYME]the first time to pull message, but pull request offset larger than broker consume offset. pullRequest: {} NewOffset: {}",
                            pullRequest, offset);
                    }
 
                    //设置previouslyLocked为true
                    pullRequest.setPreviouslyLocked(true);
                    //重设消费点位
                    pullRequest.setNextOffset(offset);
                }
            } else {
    
    
                //如果没有被锁住,那么延迟3s发送拉取消息请求
                this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
                log.info("pull message later because not locked in broker, {}", pullRequest);
                return;
            }
        }
 
        //获取topic对应的SubscriptionData订阅关系
        final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
        //如果订阅信息为null,则表示发现消费者的订阅失败
        if (null == subscriptionData) {
    
    
            //延迟3s发送拉取消息请求
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            log.warn("find the consumer's subscription failed, {}", pullRequest);
            return;
        }
 
        //起始时间
        final long beginTimestamp = System.currentTimeMillis();
 
        /**
         * 4. 创建拉取消息的回调函数对象,当拉取消息的请求返回之后,将会指定回调函数
         */
        //TODO: 构建消息处理的回调对象,它的非常重要的,等从broker拉取消息后(这里是从broker拉取消息成功后才执行的),会交给它来处理
        PullCallback pullCallback = new PullCallback() {
    
    
            @Override
            public void onSuccess(PullResult pullResult) {
    
    
                if (pullResult != null) {
    
    
                    /**
                     * 拉取成功,开始处理从broker读取到的消息
                     * 将二进制内容转换成MessageExt对象,并根据TAG的值进行过滤
                     */
                    pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
                        subscriptionData);
 
                    switch (pullResult.getPullStatus()) {
    
    
                        //TODO: 发现了消息
                        case FOUND:
                            long prevRequestOffset = pullRequest.getNextOffset();
                            //TODO: 更新nextOffset的值
                            pullRequest.setNextOffset(pullResult.getNextBeginOffset());
                            long pullRT = System.currentTimeMillis() - beginTimestamp;
                            DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(pullRequest.getConsumerGroup(),
                                pullRequest.getMessageQueue().getTopic(), pullRT);
 
                            long firstMsgOffset = Long.MAX_VALUE;
                            /**
                             * TODO:如果没有消息则立即执行,立即拉取的意思是继续将PullRequest放入队列中
                             * 这样take()方法将不会再阻塞,然后继续从broker拉取消息,从而达到持续从broker拉取消息
                             */
                            if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) {
    
    
                                //拉取消息
                                DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                            } else {
    
    
                                //拉取的消息不为空
                                firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset();
 
                                DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(),
                                    pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size());
 
                                //TODO: 将本次读取到的所有信息(经过TAG/SQL过滤)保存到本地缓存队列processQueue中
                                boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
                                /**
                                 * TODO: 构建consumeRequest,将消息提交到线程池中,由ConsumerMessageService 进行消费
                                 * 由于我们的是普通消息(不是顺序消息),所以由ConsumeMessageConcurrentlyService类来消费消息
                                 * ConsumeMessageConcurrentlyService内部会创建一个线程池ThreadPoolExecutor,这个xcc非常重要,消息最终将提交到这个线程池中
                                 */
                                DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(
                                    pullResult.getMsgFoundList(),
                                    processQueue,
                                    pullRequest.getMessageQueue(),
                                    dispatchToConsume);
 
                                /**
                                 * 上面是异步消费,然后这里是将PullRequest放入队列中,这样take()方法将不会阻塞
                                 * 然后继续从broker中拉取消息,从而到达持续从broker中拉取消息
                                 * 延迟pullInterval 时间再去拉取消息:
                                 *      这里有一个 pullInterval参数,表示间隔多长时间在放入队列中(实际上就是间隔多长时间再去broker拉取消息)。当消费者消费速度比生产者快的时候,可以考虑设置这个值,这样可以避免大概率拉取到空消息。
                                 *
                                 */
                                if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) {
    
    
                                    DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest,
                                        DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
                                } else {
    
    
                                    //立即拉取消息
                                    DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                                }
                            }
 
                            if (pullResult.getNextBeginOffset() < prevRequestOffset
                                || firstMsgOffset < prevRequestOffset) {
    
    
                                log.warn(
                                    "[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}",
                                    pullResult.getNextBeginOffset(),
                                    firstMsgOffset,
                                    prevRequestOffset);
                            }
 
                            break;
                        case NO_NEW_MSG:
                        case NO_MATCHED_MSG:
                            pullRequest.setNextOffset(pullResult.getNextBeginOffset());
 
                            DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);
 
                            DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                            break;
                        case OFFSET_ILLEGAL:
                            log.warn("the pull request offset illegal, {} {}",
                                pullRequest.toString(), pullResult.toString());
                            pullRequest.setNextOffset(pullResult.getNextBeginOffset());
 
                            pullRequest.getProcessQueue().setDropped(true);
                            DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() {
    
    
 
                                @Override
                                public void run() {
    
    
                                    try {
    
    
                                        DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(),
                                            pullRequest.getNextOffset(), false);
 
                                        DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue());
 
                                        DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue());
 
                                        log.warn("fix the pull request offset, {}", pullRequest);
                                    } catch (Throwable e) {
    
    
                                        log.error("executeTaskLater Exception", e);
                                    }
                                }
                            }, 10000);
                            break;
                        default:
                            break;
                    }
                }
            }
 
            /**
             * TODO: 如果拉取出现异常,则执行异常回调
             * 如果broker没有消息,则将拉取请求挂起,然后返回一个null对象给消费者,消费者如果拿到的是null,则是为异常情况,然后执行异常回调
             * 所以说,如果没有消息,则broker将拉取请求挂起,其目的是如果有消息到达,能立即写给消费者;同时消费者也会每隔3s去broker拉取一次,如果这次依然没有消息,则继续将本次请求挂起
             */
            @Override
            public void onException(Throwable e) {
    
    
                if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
    
    
                    log.warn("execute the pull request exception", e);
                }
 
                //TODO: 延迟3s钟,将PullRequest对象再次放入队列pullRequestQueue中,等待再次take(),然后继续拉取消息的逻辑
                DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            }
        };
 
        /**
         * 5. 是否允许上报消费点位
         */
        boolean commitOffsetEnable = false;
        long commitOffsetValue = 0L;
        //如果是集群消费模式
        if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
    
    
            //从本地内存offsetTable读取commitOffsetValue
            commitOffsetValue = this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY);
            if (commitOffsetValue > 0) {
    
    
                //如果本地内存有关于此mq的offset,那么设置为true,表示可以上波消费点位给broker
                commitOffsetEnable = true;
            }
        }
 
        String subExpression = null;
        boolean classFilter = false;
        //classFilter相关处理
        SubscriptionData sd = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
        if (sd != null) {
    
    
            if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) {
    
    
                subExpression = sd.getSubString();
            }
 
            classFilter = sd.isClassFilterMode();
        }
 
        //系统标识
        int sysFlag = PullSysFlag.buildSysFlag(
            commitOffsetEnable, // commitOffset
            true, // suspend
            subExpression != null, // subscription
            classFilter // class filter
        );
        /**
         * 6. 真正的开始拉取消息
         */
        try {
    
    
            //TODO: 与服务端交互,拉取信息
            this.pullAPIWrapper.pullKernelImpl(
                //TODO: 指定去那个queue去拉取消息
                pullRequest.getMessageQueue(),
                //TODO:表达式,就是tag/sql
                subExpression,
                //TODO:表达式类型,TAG/SQL
                subscriptionData.getExpressionType(),
                subscriptionData.getSubVersion(),
                //TODO: 第一次拉取它的值是0,nextOffset参数的非常重要的,RocketMQ就是通过这个参数来保证消息不会重复消息的(宏观上)将上面的参数信息封装到PullMessageRequestHeader对象中,然后拉取消息
                pullRequest.getNextOffset(),
                //TODO: 这个参数值默认是32
                this.defaultMQPushConsumer.getPullBatchSize(),
                this.defaultMQPushConsumer.getPullBatchSizeInBytes(),
                sysFlag,
                commitOffsetValue,
                //TODO: 当consumer拉取消息但broker没有时,此时broker会将请求挂起,默认是15s
                BROKER_SUSPEND_MAX_TIME_MILLIS,
                CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
                //TODO: 异步
                CommunicationMode.ASYNC,
                //TODO: 回调对象
                pullCallback
            );
        } catch (Exception e) {
    
    
            log.error("pullKernelImpl exception", e);
            //拉取消息异常,延迟3s发送拉取消息请求
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
        }
    }

5.1、pullKernelImpl拉取消息

PullAPIWrapper的方法,用于拉取消息。

首先获取指定brokerName的broker地址,默认获取master地址,如果由建议的拉取地址,则获取建议的broker地址,没找到broker地址,那么更新topic路由信息再获取一次。

找到了broker之后,校验版本号,通过之后构造PullMessageRequestHeader请求头,然后调用MQClientAPIImpl#pullMessage方法发送请求,进行消息拉取。

  /**
     * PullAPIWrapper的方法
     * @param mq                            消息消费队列
     * @param subExpression                 消息订阅子模式subscribe(topicName,"模式")
     * @param expressionType
     * @param subVersion                    版本
     * @param offset                        pullRequest.getNextOffset()
     * @param maxNums                       defaultMQPushConsumer.getPullBatchSize()
     * @param maxSizeInBytes
     * @param sysFlag                       系统标记,FLAG_COMMIT_OFFSET FLAG_SUSPEND FLAG_SUBSCRIPTION
     * @param commitOffset                  当前消息队列,commitlog日志中当前的最新偏移量(内存中)
     * @param brokerSuspendMaxTimeMillis    允许的broker暂停的事件,毫秒为单位,默认15s
     * @param timeoutMillis                 超时事件,默认30s
     * @param communicationMode             SYNC ASYNC ONEWAY
     * @param pullCallback                  pull回调
     * @return
     * @throws MQClientException
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    public PullResult pullKernelImpl(
        final MessageQueue mq,
        final String subExpression,
        final String expressionType,
        final long subVersion,
        final long offset,
        final int maxNums,
        final int maxSizeInBytes,
        final int sysFlag,
        final long commitOffset,
        final long brokerSuspendMaxTimeMillis,
        final long timeoutMillis,
        final CommunicationMode communicationMode,
        final PullCallback pullCallback
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
    
    
        //获取指定brokerName的broker地址,默认获取Master地址,如果有建议的拉取地址,则获取建议的broker地址,最后封装成FindBrokerResult
        FindBrokerResult findBrokerResult =
            this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq),
                this.recalculatePullFromWhichNode(mq), false);
        //如果本地找不到broker地址,那么更新topic路由信息再获取一次
        if (null == findBrokerResult) {
    
    
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
            findBrokerResult =
                this.mQClientFactory.findBrokerAddressInSubscribe(this.mQClientFactory.getBrokerNameFromMessageQueue(mq),
                    this.recalculatePullFromWhichNode(mq), false);
        }
 
 
        //找到了broker
        if (findBrokerResult != null) {
    
    
            {
    
    
                // check version
                //检查版本
                if (!ExpressionType.isTagType(expressionType)
                    && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) {
    
    
                    throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
                        + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
                }
            }
            int sysFlagInner = sysFlag;
 
            //如果拉取消息的是slave broker,不进行消息确认
            if (findBrokerResult.isSlave()) {
    
    
                sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
            }
 
            /**
             * 构造PullMessageRequestHeader请求头
             */
            PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
            //TODO: 消费者组
            requestHeader.setConsumerGroup(this.consumerGroup);
            //TODO: topic
            requestHeader.setTopic(mq.getTopic());
            //TODO: 消费哪个queue,重平衡服务做的就是这个
            requestHeader.setQueueId(mq.getQueueId());
            //TODO: 从哪个queue的offset开始消费,拉取偏移量
            requestHeader.setQueueOffset(offset);
            //TODO: pullBatchSize最大拉取消息数量, 默认是32
            requestHeader.setMaxMsgNums(maxNums);
            //TODO: 系统标记
            requestHeader.setSysFlag(sysFlagInner);
            //TODO: 提交的消费点位
            requestHeader.setCommitOffset(commitOffset);
            //TODO: 当consumer拉取消息但broker没有时,此时broker会将请求挂起,默认是15s
            requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
            //订阅关系表达式,它仅支持如“tag1 | | tag2 | | tag3”,如果为 null 或 *,则表示订阅全部
            requestHeader.setSubscription(subExpression);
            //订阅关系版本
            requestHeader.setSubVersion(subVersion);
            requestHeader.setMaxMsgBytes(maxSizeInBytes);
            //表达式类型 TAG 或者 SQL92
            requestHeader.setExpressionType(expressionType);
            requestHeader.setBname(mq.getBrokerName());
 
            String brokerAddr = findBrokerResult.getBrokerAddr();
            if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
    
    
                brokerAddr = computePullFromWhichFilterServer(mq.getTopic(), brokerAddr);
            }
 
            //TODO: 调用MQClientAPIImpl#pullMessage方法发送请求,去拉取具体的消息
            PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
                brokerAddr,
                requestHeader,
                timeoutMillis,
                communicationMode,
                pullCallback);
 
            return pullResult;
        }
 
        throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
    }

5.2 pullMessage发起拉取消息请求

MQClientAPIImpl的方法,首先构建构建请求命令对象,请求Code为PULL_MESSAGE,然后根据设置的消息拉取模式,调用不同的方法发起不同请求进行消息拉取。

  /**
     * MQClientAPIImpl的方法
     * 创建拉取消息的netty指令,发送到broker
     *
     * @param addr                  broker地址
     * @param requestHeader         请求头
     * @param timeoutMillis         消费者消费拉取超时事件,默认30s
     * @param communicationMode     消息拉取模式,默认异步拉取
     * @param pullCallback          拉取到消息之后调用的回调函数
     * @return 拉取结果
     * @throws RemotingException
     * @throws MQBrokerException
     * @throws InterruptedException
     */
    public PullResult pullMessage(
        final String addr,
        final PullMessageRequestHeader requestHeader,
        final long timeoutMillis,
        final CommunicationMode communicationMode,
        final PullCallback pullCallback
    ) throws RemotingException, MQBrokerException, InterruptedException {
    
    
        RemotingCommand request;
        //判断拉取消息指令是 LITE_PULL_MESSAGE 还是 PULL_MESSAGE
        if (PullSysFlag.hasLitePullFlag(requestHeader.getSysFlag())) {
    
    
            //TODO: 构建拉取消息的netty指令:LITE_PULL_MESSAGE
            request = RemotingCommand.createRequestCommand(RequestCode.LITE_PULL_MESSAGE, requestHeader);
        } else {
    
    
            //TODO:构建拉取消息的netty指令:PULL_MESSAGE
            request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);
        }
 
        switch (communicationMode) {
    
    
            case ONEWAY:
                assert false;
                return null;
            case ASYNC:
                //TODO: push模式默认异步拉取消息,将拉取消息的结果交给 PullCalBack 处理
                this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
                return null;
            case SYNC:
                //TODO: 同步拉取消息
                return this.pullMessageSync(addr, request, timeoutMillis);
            default:
                assert false;
                break;
        }
 
        return null;
    }

push消费模式默认异步拉取消息,即调用pullMessageAsync方法拉取消息。

pullMessageAsync方法内部调用remotingClient.invokeAsync方法,基于netty给broker发送异步消息,设置一个InvokeCallback回调对象。

InvokeCallback#operationComplete方法将会在得到结果之后进行回调,内部调用pullCallback的回调方法。

在回调方法中,如果解析到了响应结果,那么调用pullCallback#onSuccess方法处理,否则调用pullCallback#onException方法处理。

  /**
     * MQClientAPIImpl的方法
     * 异步的拉取消息,并且触发回调函数
     * @param addr              broker地址
     * @param request           请求命令对象
     * @param timeoutMillis     消费者消息拉取超时时间,默认30s
     * @param pullCallback      拉取到消息之后调用的回调函数
     * @throws RemotingException
     * @throws InterruptedException
     */
    private void pullMessageAsync(
        final String addr,
        final RemotingCommand request,
        final long timeoutMillis,
        final PullCallback pullCallback
    ) throws RemotingException, InterruptedException {
    
    
        /**
         * 基于netty给broker发送异步消息,设置一个InvokeCallback回调对象
         * InvokeCallback#operationComplete方法将会再得到结果之后进行回调,内部调用pullCallback的回调方法
         */
        this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
    
    
            /**
             * 异步执行的回调方法
             * @param responseFuture
             */
            @Override
            public void operationComplete(ResponseFuture responseFuture) {
    
    
                //返回命令对象
                RemotingCommand response = responseFuture.getResponseCommand();
                if (response != null) {
    
    
                    try {
    
    
                        //解析响应获取结果
                        PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);
                        assert pullResult != null;
                        //如果解析到了结果,那么调用pullCallback#onSucess方法处理
                        pullCallback.onSuccess(pullResult);
                    } catch (Exception e) {
    
    
                        pullCallback.onException(e);
                    }
                } else {
    
    
                    //没有结果,就调用onException方法处理异常
                    if (!responseFuture.isSendRequestOK()) {
    
    
                        //发送失败
                        pullCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
                    } else if (responseFuture.isTimeout()) {
    
    
                        //超时
                        pullCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
                            responseFuture.getCause()));
                    } else {
    
    
                        pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
                    }
                }
            }
        });
    }

消息拉取由PullMessageService服务实现,PullMessageService继承了ServiceThread,因此它也是一个异步线程任务,将会在线程任务中,不断循环的从pullRequestQueue中阻塞式的获取并移除队列的头部数据,即拉取消息的请求,然后调用pullMessage方法根据请求去broker中拉取消息。

发起拉取请求之前,会进行一系列的校验,例如服务状态校验、流控校验、顺序消费和并发消费的校验,都通过之后,创建拉取消息的回调函数对象PullCallback,当拉取消息的请求返回之后,将会调用回调函数。最后通过pullAPIWrapper.pullKernelImpl方法真正的拉取消息。

最终,拉取消息请求Code为PULL_MESSAGE,push消费模式默认异步拉取消息,即调用pullMessageAsync方法拉取消息。

猜你喜欢

转载自blog.csdn.net/weixin_47068446/article/details/129967331