RocketMQ源码学习 (六) 消费者-消费者拉消息源码学习

学完了之前的消费者拉取消息的原理后,本文将探讨消费者拉取消息的源码层面

1. pullMessage方法

该方法是个很长的过程,让我们一起来看看这玩意。在看这块代码之前,先看一下PullRequest 对象的内部主要构造;

public class PullRequest {
    // 消费者组
    private String consumerGroup;
    // 队列元数据
    private MessageQueue messageQueue;
    // 处理队列(消费者本地的队列快照,从服务器拉取下来的消息要先放到该快照队列内..消费任务 被消费的消息 需要从 该队列 移除走)
    private ProcessQueue processQueue;
    // 本次拉消息请求,使用offset (服务器端 需要根据该offset 进行定位 消息位置,然后才可以获取一批消息)
    private long nextOffset;
}

接下来上大餐,先看并发消费的逻辑:

    /**
     * @param pullRequest 拉消息请求对象
     */
    public void pullMessage(final PullRequest pullRequest) {
        // 获取拉消息队列在消费者端的快照队列
        final ProcessQueue processQueue = pullRequest.getProcessQueue();
        // 条件成立:说明该队列状态是删除态(可能是rbl之后,被转移到其它消费者了),这里不再为该队列拉消息
        if (processQueue.isDropped()) {
            log.info("the pull request[{}] is dropped.", pullRequest.toString());
            return;
        }

        // 设置本次拉消息时间
        pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());

        try {
            this.makeSureStateOK();
        } catch (MQClientException e) {
            log.warn("pullMessage exception, consumer state not ok", e);
            // 如果当前消费者状态不是运行状态,则拉消息任务 延迟 3秒之后再执行
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            // 返回
            return;
        }
        // 条件成立:说明消费者处于“暂停”状态
        if (this.isPause()) {
            log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup());
            // 暂停状态,消费者的拉消息任务 延迟 1秒之后再执行..
            this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND);
            return;
        }

        // 获取消费者本地该 queue 快照内缓存的消息数量
        long cachedMessageCount = processQueue.getMsgCount().get();
        // 获取消费者本地该 queue 快照内缓存的消息容量size
        long cachedMessageSizeInMiB = processQueue.getMsgSize().get() / (1024 * 1024);

        // 条件成立:说明消费者本地快照内还有 “1000”条消息 未被消费..本次拉消息请求 将被延迟...
        if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) {
            // 拉消息请求 延迟 50 毫秒..
            this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
            // 每流控1000次 打印一次日志..
            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;
        }
        // 条件成立:说明消费者本地快照内还有 100mib消息未被消费..本次拉消息请求 将被延迟...
        if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) {
            // 拉消息请求 延迟 50 毫秒..
            this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
            // 每流控1000次 打印一次日志..
            if ((queueFlowControlTimes++ % 1000) == 0) {
                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;
        }
        if (!this.consumeOrderly) {
            // CASE:并发消费

            // processQueue.getMaxSpan() 获取快照队列内 最后一条消息 和 第一条消息 的offset 差值
            // 条件成立:差值 大于流控限制“2000”  (注意:说明不了 processQueue内 有 2000 条消息 ,因为存在 服务器端 和 客户端 过滤逻辑)
            if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) {
                // 延迟 拉消息请求 50 毫秒后执行..
                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 {
            // CASE:顺序消费 (回头再聊..)
            if (processQueue.isLocked()) {
                if (!pullRequest.isLockedFirst()) {
                    final long offset = this.rebalanceImpl.computePullFromWhere(pullRequest.getMessageQueue());
                    boolean brokerBusy = offset < pullRequest.getNextOffset();
                    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);
                    }

                    pullRequest.setLockedFirst(true);
                    pullRequest.setNextOffset(offset);
                }
            } else {
                this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
                log.info("pull message later because not locked in broker, {}", pullRequest);
                return;
            }
        }
        // 获取本次拉消息请求主题的订阅数据
        final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());

        // 什么时候subscriptionData == null?  unsubscribe(主题) 给删除了,这种情况下,该条件会成立。
        // 最终 rbl 程序 会对比 订阅集合,将移除的订阅主题的processQueue的dropped状态设置为true,然后 该queue对应的pullRequest请求 ,就会退出了..
        if (null == subscriptionData) {
            // 延迟3秒
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            log.warn("find the consumer's subscription failed, {}", pullRequest);
            return;
        }
        // 拉消息的开始时间
        final long beginTimestamp = System.currentTimeMillis();
        // 拉消息处理回调对象(内部代码,将pullResult交给 “拉消息结果处理回调对象”,调用它的onSuccess 方法..)
        PullCallback pullCallback = new PullCallback() {
            @Override
            public void onSuccess(PullResult pullResult) {
                if (pullResult != null) {
                    // 预处理 pullResult 结果
                    pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
                        subscriptionData);
                    switch (pullResult.getPullStatus()) {
                        case FOUND://(正常从服务器拉取到消息)
                            long prevRequestOffset = pullRequest.getNextOffset();
                            // 更新pullRequest对象的 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;
                            // 什么时候条件成立? 客户端消息过滤导致消息全部被过滤掉了...
                            if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) {
                                // 立马发起下一次拉消息
                                DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                            } else {
                                // 一般走这里。
                                // 获取本次拉取消息的第一条消息的 offset
                                firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset();                                DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(),
                                    pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size());
                                // 将服务器拉取的消息list 加入到 消费者本地该queue的 processQueue 内
                                boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
                                // 提交“消费任务”
                                // 参数1:msgFoundList,从服务器端拉取下来的消息 并且 客户端再次过滤后 剩余的消息
                                // 参数2:客户端mq处理队列
                                // 参数3:mq
                                // 参数4:并发消费服务此参数 无效,该参数只有顺序消费服务才有效
                                DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(
                                    pullResult.getMsgFoundList(),
                                    processQueue,
                                    pullRequest.getMessageQueue(),
                                    dispatchToConsume);
                                if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) {
                                    DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest,
                                        DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
                                } else {
                                    // 将更新过 pullRequest.nextBeginOffset 字段的  pullRequest对象,再次放到 pullMessageService 的 queue中。
                                    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:// NO_NEW_MSG || NO_MATCHED_MSG 都表示 本次pull 没有新的 可消费的 消息\
                            // 更新 pullRequest nextOffset 字段
                            pullRequest.setNextOffset(pullResult.getNextBeginOffset());
                            // 更新 offsetStore 该messageQueue 最新的 offset
                            DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);

                            // 将 pullRequest 再次放到 pullMessageService 的queue 内,方便再次发起该queue的拉消息 请求。
                            DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                            break;
                        case OFFSET_ILLEGAL:// 什么时候 OFFSET_ILLEGAL  ? 本次pull时使用的 offset 是无效的,
                            // 即 offset > maxOffset || offset  < minOffset

                            log.warn("the pull request offset illegal, {} {}",
                                pullRequest.toString(), pullResult.toString());
                            // 调整pullRequest nextOffset 为 正确的 offset
                            // (offset > maxOffset  => maxOffset ,offset  < minOffset => minOffset)
                            pullRequest.setNextOffset(pullResult.getNextBeginOffset());

                            // 设置该messageQueue 在消费者端的 processQueue 为删除状态,如果有该queue的消费任务,该消费任务会马上停止任务。
                            pullRequest.getProcessQueue().setDropped(true);


                            // 提交一个延迟任务,10秒钟之后执行
                            DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() {

                                @Override
                                public void run() {
                                    try {
                                        // 更新offsetStore 该 messageQueue 的offset 为正确值,注意:increaseOnly = false,内部直接替换,不做比较操作。
                                        DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(),
                                            pullRequest.getNextOffset(), false);


                                        // 持久化该messageQueue的offset 到 Broker 端。
                                        DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue());

                                        // 删除该消费者该messageQueue对应的 processQueue
                                        DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue());

                                        // 注意,这里并没有再次提交 pullRequest 到 pullMessageService 的队列,那岂不该队列不再拉消息了?
                                        // 并不会..rbl程序 会重建该队列的 processQueue,重建完之后,会再为该 queue 创建 pullRequest 对象,
                                        // 放入到 pullMessageService的任务queue内。
                                        log.warn("fix the pull request offset, {}", pullRequest);
                                    } catch (Throwable e) {
                                        log.error("executeTaskLater Exception", e);
                                    }
                                }
                            }, 10000);
                            break;
                        default:
                            break;
                    }
                }
            }

            @Override
            public void onException(Throwable e) {
                if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("execute the pull request exception", e);
                }

                DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
            }
        };

        // 是否提交消费者本地该队列的offset
        boolean commitOffsetEnable = false;
        // 该队列在消费者本地的offset
        long commitOffsetValue = 0L;
        if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
            // 从offsetStore内读取该队列的offset
            commitOffsetValue = this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY);
            if (commitOffsetValue > 0) {
                commitOffsetEnable = true;
            }
        }


        // 过滤表达式
        String subExpression = null;
        // 是否类过滤模式
        boolean classFilter = false;
        // 获取该主题的订阅数据
        SubscriptionData sd = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());

        if (sd != null) {
            if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) {
                subExpression = sd.getSubString();
            }
            classFilter = sd.isClassFilterMode();
        }

        // sysFlag 高4位未使用,低4位使用
        // 第一位:表示是否提交消费者本地该队列的offset  (一般是 1)
        // 第二位:表示是否允许服务器端进行长轮询     (一般是 1)
        // 第三位:表示是否提交消费者本地该主题的订阅数据  (一般是 0)
        // 第四位:表示是否为类过滤   (一般是 0)
        int sysFlag = PullSysFlag.buildSysFlag(
            commitOffsetEnable, // commitOffset
            true, // suspend
            subExpression != null, // subscription
            classFilter // class filter
        );


        try {
            // 参数1:messageQueue,拉消息队列
            // 参数2:subExpression,过滤表达式 一般是 null
            // 参数3:表达式类型,一般是 tag
            // 参数4:客户端版本
            // 参数5:nextOffset,本次拉消息offset(重要)
            // 参数6:pullBatchSize,拉消息最多消息条数限制
            // 参数7:sysFlag
            // 参数8:commitOffsetValue,消费者本地该队列的消费进度
            // 参数9:BROKER_SUSPEND_MAX_TIME_MILLIS,控制服务器端长轮询时 最长 hold 的时间(15秒)
            // 参数10:CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,网络调用超时时间限制 (30秒)
            // 参数11:CommunicationMode.ASYNC,RPC调用模式 使用的是异步模式
            // 参数12:pullCallback,拉消息结果回调处理对象
            this.pullAPIWrapper.pullKernelImpl(
                pullRequest.getMessageQueue(),
                subExpression,
                subscriptionData.getExpressionType(),
                subscriptionData.getSubVersion(),
                pullRequest.getNextOffset(),
                this.defaultMQPushConsumer.getPullBatchSize(),
                sysFlag,
                commitOffsetValue,
                BROKER_SUSPEND_MAX_TIME_MILLIS,
                CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
                CommunicationMode.ASYNC,
                pullCallback
            );
        } catch (Exception e) {
            log.error("pullKernelImpl exception", e);
            this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
        }
    }

2 pullKernelImpl方法

上文中的pullKernelImpl方法的逻辑如下:

    // 参数1:messageQueue,拉消息队列
    // 参数2:subExpression,过滤表达式 一般是 null
    // 参数3:表达式类型,一般是 tag
    // 参数4:客户端版本
    // 参数5:nextOffset,本次拉消息offset(重要)
    // 参数6:pullBatchSize,拉消息最多消息条数限制
    // 参数7:sysFlag
    // 参数8:commitOffsetValue,消费者本地该队列的消费进度
    // 参数9:BROKER_SUSPEND_MAX_TIME_MILLIS,控制服务器端长轮询时 最长 hold 的时间(15秒)
    // 参数10:CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,网络调用超时时间限制 (30秒)
    // 参数11:CommunicationMode.ASYNC,RPC调用模式 使用的是异步模式
    // 参数12:pullCallback,拉消息结果回调处理对象
    public PullResult pullKernelImpl(
        final MessageQueue mq,
        final String subExpression,
        final String expressionType,
        final long subVersion,
        final long offset,
        final int maxNums,
        final int sysFlag,
        final long commitOffset,
        final long brokerSuspendMaxTimeMillis,
        final long timeoutMillis,
        final CommunicationMode communicationMode,
        final PullCallback pullCallback
    ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {

        // 查询指定brokerName的地址信息,FindBrokerResult类的内容主要是:地址和节点角色(是否为slave节点)
        FindBrokerResult findBrokerResult =

                // 参数1:brokerName
                // 参数2:this.recalculatePullFromWhichNode(mq)  (可能0,也可能 1)
                // 参数3:false
                // 作用:获取该mq推荐的主机id,如果不是空则返回,如果为空则返回返回主节点id ,值为0
            this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                this.recalculatePullFromWhichNode(mq), false);


        if (null == findBrokerResult) {
            // 如果为空,到nameserver获取指定 topic 的路由数据,路由数据包含 主机信息..
            this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
            findBrokerResult =
                this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                    this.recalculatePullFromWhichNode(mq), false);
        }

        if (findBrokerResult != null) {
            {
                // check version
                if (!ExpressionType.isTagType(expressionType)
                    && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) {
                    // RMQ 在 4.1.0 之后才支持的 非tag过滤
                    throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
                        + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
                }
            }

            int sysFlagInner = sysFlag;

            // 条件成立:说明 findBrokerResult 表示的主机为 slave节点,slave不存储offset信息
            if (findBrokerResult.isSlave()) {
                // 将 sysFlag 标记位中 CommitOffset的位 设置为 0
                sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
            }

            // 创建 header 对象,并且初始化值,将业务参数全部封装进去
            PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
            requestHeader.setConsumerGroup(this.consumerGroup);
            requestHeader.setTopic(mq.getTopic());
            requestHeader.setQueueId(mq.getQueueId());
            requestHeader.setQueueOffset(offset);
            requestHeader.setMaxMsgNums(maxNums);
            requestHeader.setSysFlag(sysFlagInner);
            requestHeader.setCommitOffset(commitOffset);
            requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
            requestHeader.setSubscription(subExpression);
            requestHeader.setSubVersion(subVersion);
            requestHeader.setExpressionType(expressionType);


            // 获取brokerAddr
            String brokerAddr = findBrokerResult.getBrokerAddr();

            if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
                brokerAddr = computePullFromWhichFilterServer(mq.getTopic(), brokerAddr);
            }
            
            // 参数1:brokerAddr,本次拉消息请求的服务器地址
            // 参数2:requestHeader,拉消息业务参数封装对象
            // 参数3:timeoutMillis,网络调用超时限制 30秒
            // 参数4:communicationMode,RPC调用模式 这里是 异步模式
            // 参数5:pullCallback,拉消息结果处理对象
            PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
                brokerAddr,
                requestHeader,
                timeoutMillis,
                communicationMode,
                pullCallback);

            return pullResult;
        }

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

    /**
     * @param mq 拉消息的队列
     */
    public long recalculatePullFromWhichNode(final MessageQueue mq) {

        if (this.isConnectBrokerByUser()) {
            return this.defaultBrokerId;
        }

        // 获取该mq推荐的主机id
        AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);

        if (suggest != null) {
            return suggest.get();
        }

        // 返回主节点id 0
        return MixAll.MASTER_ID;
    }

3 pullMessageAsync方法

上述代码里面的pullMessage方法为拉消息里,会创建 网络层传输对象 RemotingCommand ,该对象封装了 requestHeader,然后会走到异步模式的的pullMessageAsync方法中去,该方法的核心实现,代码逻辑如下:


    private void pullMessageAsync(
        final String addr,
        final RemotingCommand request,
        final long timeoutMillis,
        final PullCallback pullCallback
    ) throws RemotingException, InterruptedException {

        // invokeCallback 最重要
        // invokeAsync 内部会为本次请求 创建 一个 ResponseFuture 对象,放入到 remotingClient的 responseFutureTable 中,key
        // 是 request.opaque。
        // ResponseFuture { 1. opaque 2.invokeCallback  3. response}
        // 当服务器端 响应 客户端时,会根据response.opaque 值找到 当前 responseFuture 对象,将结果设置到 responseFuture.response 字段。
        // 再接下来,会检查该 responseFuture.invokeCallback 是否有值,如果有值,则说明需要回调处理。
        // 再接下来,就将该invokeCallback封装成任务,提交到 remotingClient 的 公共线程内执行,执行invokeCallback  operationComplete 方法。
        // 传递参数:responseFuture
        this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
            /**
             * 调用时机:服务器端响应客户端之后
             * @param responseFuture
             */
            @Override
            public void operationComplete(ResponseFuture responseFuture) {

                // 获取服务器端响应数据 response
                RemotingCommand response = responseFuture.getResponseCommand();

                if (response != null) {
                    try {
                        // 从response内提取出来拉消息结果对象,会创建PullResultExt对象,根据下面的参数去new:
					        // 参数1:pullStatus 状态
					        // 参数2:nextBeginOffset
					        // 参数3:minOffset
					        // 参数4:maxOffset
					        // 参数5:msgFoundList ,这里null
					        // 参数6:suggestWhichBrokerId,服务器端推荐下次该mq拉消息时 使用的 主机id
					        // 参数7:messageBinary,消息列表二进制表示
                        PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);

                        assert pullResult != null;
                        // 将pullResult交给 “拉消息结果处理回调对象”,调用它的onSuccess 方法..
                        pullCallback.onSuccess(pullResult);
                    } catch (Exception e) {
                        pullCallback.onException(e);
                    }
                } else {
                    if (!responseFuture.isSendRequestOK()) {
                        pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
                    } else if (responseFuture.isTimeout()) {
                        pullCallback.onException(new MQClientException("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()));
                    }
                }
            }
        });
    }

4 processPullResult方法

processPullResult方法是在onSuccess方法里被调用,预处理PullRequest结果,代码如下:


    /**
     * 预处理 拉消息结果 ,主要将服务器端指定mq的拉消息下一次的推荐节点 保存到 pullFromWhichNodeTable 中,
     * 以及消息客户端过滤
     */
    public PullResult processPullResult(final MessageQueue mq, final PullResult pullResult,
        final SubscriptionData subscriptionData) {
        PullResultExt pullResultExt = (PullResultExt) pullResult;
        // 更新 pullFromWhichNodeTable 内该mq的下次查询推荐 brokerId
        this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());


        if (PullStatus.FOUND == pullResult.getPullStatus()) {
            // 使用 缓冲区 表示 messageBinary
            ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary());
            // 解码..
            List<MessageExt> msgList = MessageDecoder.decodes(byteBuffer);

            // msgListFilterAgain 客户端再次过滤后的 list
            List<MessageExt> msgListFilterAgain = msgList;


            // 客户端按照tag值进行过滤
            if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()) {
                msgListFilterAgain = new ArrayList<MessageExt>(msgList.size());
                for (MessageExt msg : msgList) {
                    if (msg.getTags() != null) {
                        if (subscriptionData.getTagsSet().contains(msg.getTags())) {
                            msgListFilterAgain.add(msg);
                        }
                    }
                }
            }

            // 客户端执行hook过滤
            if (this.hasHook()) {
                FilterMessageContext filterMessageContext = new FilterMessageContext();
                filterMessageContext.setUnitMode(unitMode);
                filterMessageContext.setMsgList(msgListFilterAgain);
                this.executeHook(filterMessageContext);
            }


            for (MessageExt msg : msgListFilterAgain) {
                String traFlag = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
                if (Boolean.parseBoolean(traFlag)) {
                    msg.setTransactionId(msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
                }
                // 给消息添加三个property:1. 队列最小Offset 2.队列最大Offset 3.消息归属brokerName
                MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET,
                    Long.toString(pullResult.getMinOffset()));
                MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET,
                    Long.toString(pullResult.getMaxOffset()));
                msg.setBrokerName(mq.getBrokerName());
            }

            // 将再次过滤后的消息list,保存到 pullResult
            pullResultExt.setMsgFoundList(msgListFilterAgain);
        }

        // 将 pullResult的messageBinary 设置为null,help GC
        pullResultExt.setMessageBinary(null);

        // 返回预处理完的 pullResult
        return pullResult;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值