RocketMQ源码(九):consumer消息拉取(二)

RocketMQ源码(一):NameServer的启动
RocketMQ源码(二):broker的启动(一)
RocketMQ源码(三):broker的启动(二)
RocketMQ源码(四):producer的启动
RocketMQ源码(五):producer发送消息
RocketMQ源码(六):broker接收消息
RocketMQ源码(七):consumer的启动
RocketMQ源码(八):consumer消息拉取(一)
当consumer向broker发起RequestCode.PULL_MESSAGE消息拉取请求时,broker是怎么处理的呢?带着这个疑问,就开始分析消息拉取过程中broker端的处理流程

public void registerProcessor() {
    ******
    /**
     * PullMessageProcessor
     */
    this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, this.pullMessageExecutor);
    this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList);

    /**
     * ReplyMessageProcessor
     */
    ReplyMessageProcessor replyMessageProcessor = new ReplyMessageProcessor(this);
    replyMessageProcessor.registerSendMessageHook(sendMessageHookList);
    ******
}

从BrokerController的registerProcessor方法注册的事件处理器来看,处理消息拉取请求的是PullMessageProcessor这个类,因此接下来就是分析PullMessageProcessor.processRequest()

public RemotingCommand processRequest(final ChannelHandlerContext ctx,
    RemotingCommand request) throws RemotingCommandException {
    return this.processRequest(ctx.channel(), request, true);
}

接下来的processRequest方法特别长,因此拆分成几部分来看

// 构造返回
RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class);
final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader();
final PullMessageRequestHeader requestHeader =
    (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class);

response.setOpaque(request.getOpaque());

log.debug("receive PullMessage request command, {}", request);
// 校验当前 broker 是否可读
if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) {
    response.setCode(ResponseCode.NO_PERMISSION);
    response.setRemark(String.format("the broker[%s] pulling message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1()));
    return response;
}
// 查询或者自动创建 consumerGroup 对应的 subscriptionGroupConfig
// 保存subscriptionGroupConfig信息到C:\Users\xxx\store\config\subscriptionGroup.json中
SubscriptionGroupConfig subscriptionGroupConfig =
    this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup());
if (null == subscriptionGroupConfig) {
    response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST);
    response.setRemark(String.format("subscription group [%s] does not exist, %s", requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)));
    return response;
}

if (!subscriptionGroupConfig.isConsumeEnable()) {
    response.setCode(ResponseCode.NO_PERMISSION);
    response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup());
    return response;
}
// 如果没有消息时,是否在 broker 端挂起等待,默认true
final boolean hasSuspendFlag = PullSysFlag.hasSuspendFlag(requestHeader.getSysFlag());
// 是否消息拉取时就提交 offset
final boolean hasCommitOffsetFlag = PullSysFlag.hasCommitOffsetFlag(requestHeader.getSysFlag());
// 请求头是否包含订阅信息
final boolean hasSubscriptionFlag = PullSysFlag.hasSubscriptionFlag(requestHeader.getSysFlag());
// 获取挂起超时
final long suspendTimeoutMillisLong = hasSuspendFlag ? requestHeader.getSuspendTimeoutMillis() : 0;
// 获取 topicConfig
TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
if (null == topicConfig) {
    log.error("the topic {} not exist, consumer: {}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel));
    response.setCode(ResponseCode.TOPIC_NOT_EXIST);
    response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)));
    return response;
}
// 判断读的权限
if (!PermName.isReadable(topicConfig.getPerm())) {
    response.setCode(ResponseCode.NO_PERMISSION);
    response.setRemark("the topic[" + requestHeader.getTopic() + "] pulling message is forbidden");
    return response;
}
// 判断 queueId 是否超过读取队列数
if (requestHeader.getQueueId() < 0 || requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) {
    String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]",
        requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress());
    log.warn(errorInfo);
    response.setCode(ResponseCode.SYSTEM_ERROR);
    response.setRemark(errorInfo);
    return response;
}
// 订阅数据
SubscriptionData subscriptionData = null;
// consumer 的过滤数据
ConsumerFilterData consumerFilterData = null;
if (hasSubscriptionFlag) {
    try {
        // 从请求头解析订阅信息
        subscriptionData = FilterAPI.build(
            requestHeader.getTopic(), requestHeader.getSubscription(), requestHeader.getExpressionType()
        );
        if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) {
            consumerFilterData = ConsumerFilterManager.build(
                requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getSubscription(),
                requestHeader.getExpressionType(), requestHeader.getSubVersion()
            );
            assert consumerFilterData != null;
        }
    } catch (Exception e) {
        log.warn("Parse the consumer's subscription[{}] failed, group: {}", requestHeader.getSubscription(),
            requestHeader.getConsumerGroup());
        response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED);
        response.setRemark("parse the consumer's subscription failed");
        return response;
    }
} else {
    // 当consumer发送心跳时就会创建对应的consumerGroupInfo
    ConsumerGroupInfo consumerGroupInfo =
        this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup());
    if (null == consumerGroupInfo) {
        log.warn("the consumer's group info not exist, group: {}", requestHeader.getConsumerGroup());
        response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST);
        response.setRemark("the consumer's group info not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC));
        return response;
    }

    if (!subscriptionGroupConfig.isConsumeBroadcastEnable()
        && consumerGroupInfo.getMessageModel() == MessageModel.BROADCASTING) {
        response.setCode(ResponseCode.NO_PERMISSION);
        response.setRemark("the consumer group[" + requestHeader.getConsumerGroup() + "] can not consume by broadcast way");
        return response;
    }

    subscriptionData = consumerGroupInfo.findSubscriptionData(requestHeader.getTopic());
    if (null == subscriptionData) {
        log.warn("the consumer's subscription not exist, group: {}, topic:{}", requestHeader.getConsumerGroup(), requestHeader.getTopic());
        response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST);
        response.setRemark("the consumer's subscription not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC));
        return response;
    }

    if (subscriptionData.getSubVersion() < requestHeader.getSubVersion()) {
        log.warn("The broker's subscription is not latest, group: {} {}", requestHeader.getConsumerGroup(),
            subscriptionData.getSubString());
        response.setCode(ResponseCode.SUBSCRIPTION_NOT_LATEST);
        response.setRemark("the consumer's subscription not latest");
        return response;
    }
    if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) {
        consumerFilterData = this.brokerController.getConsumerFilterManager().get(requestHeader.getTopic(),
            requestHeader.getConsumerGroup());
        if (consumerFilterData == null) {
            response.setCode(ResponseCode.FILTER_DATA_NOT_EXIST);
            response.setRemark("The broker's consumer filter data is not exist!Your expression may be wrong!");
            return response;
        }
        if (consumerFilterData.getClientVersion() < requestHeader.getSubVersion()) {
            log.warn("The broker's consumer filter data is not latest, group: {}, topic: {}, serverV: {}, clientV: {}",
                requestHeader.getConsumerGroup(), requestHeader.getTopic(), consumerFilterData.getClientVersion(), requestHeader.getSubVersion());
            response.setCode(ResponseCode.FILTER_DATA_NOT_LATEST);
            response.setRemark("the consumer's consumer filter data not latest");
            return response;
        }
    }
}
// 校验表达式类型
if (!ExpressionType.isTagType(subscriptionData.getExpressionType())
    && !this.brokerController.getBrokerConfig().isEnablePropertyFilter()) {
    response.setCode(ResponseCode.SYSTEM_ERROR);
    response.setRemark("The broker does not support consumer to filter message by " + subscriptionData.getExpressionType());
    return response;
}

这一部分主要是解析请求头,以及各种权限和数据的校验

MessageFilter messageFilter;
// 重试消息是否启用过滤
if (this.brokerController.getBrokerConfig().isFilterSupportRetry()) {
    messageFilter = new ExpressionForRetryMessageFilter(subscriptionData, consumerFilterData,
        this.brokerController.getConsumerFilterManager());
} else {
    messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData,
        this.brokerController.getConsumerFilterManager());
}
// 获取消息
final GetMessageResult getMessageResult =
    this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
        requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);

根据配置实例化messageFilter,并且从messageStore中拉取到具体的消息。可以看到这里就是方法的核心,接下来继续看getMessage是如何拉取到消息的

public GetMessageResult getMessage(final String group, final String topic, final int queueId, final long offset,
    final int maxMsgNums,
    final MessageFilter messageFilter) {
    // 判断 store 是否关闭
    if (this.shutdown) {
        log.warn("message store has shutdown, so getMessage is forbidden");
        return null;
    }
    // 判断当前运行状态是否可读
    if (!this.runningFlags.isReadable()) {
        log.warn("message store is not readable, so getMessage is forbidden " + this.runningFlags.getFlagBits());
        return null;
    }

    long beginTime = this.getSystemClock().now();

    GetMessageStatus status = GetMessageStatus.NO_MESSAGE_IN_QUEUE;
    // 待查找队列的偏移量
    long nextBeginOffset = offset;
    // 当前队列的最小偏移量
    long minOffset = 0;
    // 当前队列的最大偏移量
    long maxOffset = 0;

    GetMessageResult getResult = new GetMessageResult();
    // 当前commitLog的最大偏移量
    final long maxOffsetPy = this.commitLog.getMaxOffset();
    // 查询或者新建一个 consumerQueue
    ConsumeQueue consumeQueue = findConsumeQueue(topic, queueId);
    if (consumeQueue != null) {
        // 获取 ConsumeQueue 的最小逻辑 offset
        minOffset = consumeQueue.getMinOffsetInQueue();
        // 最大逻辑offset
        maxOffset = consumeQueue.getMaxOffsetInQueue();
        // 消息队列无数据
        if (maxOffset == 0) {
            status = GetMessageStatus.NO_MESSAGE_IN_QUEUE;
            nextBeginOffset = nextOffsetCorrection(offset, 0);
        }
        // 查询的队列offset太小
        else if (offset < minOffset) {
            status = GetMessageStatus.OFFSET_TOO_SMALL;
            nextBeginOffset = nextOffsetCorrection(offset, minOffset);
        }
        // 查询的offset溢出一个
        else if (offset == maxOffset) {
            status = GetMessageStatus.OFFSET_OVERFLOW_ONE;
            nextBeginOffset = nextOffsetCorrection(offset, offset);
        }
        // 查询的队列offset过大
        else if (offset > maxOffset) {
            status = GetMessageStatus.OFFSET_OVERFLOW_BADLY;
            if (0 == minOffset) {
                nextBeginOffset = nextOffsetCorrection(offset, minOffset);
            } else {
                nextBeginOffset = nextOffsetCorrection(offset, maxOffset);
            }
        } else {
            // 从offset位置开始读取本地的consumerQueue,获取数据
            SelectMappedBufferResult bufferConsumeQueue = consumeQueue.getIndexBuffer(offset);
            // 用于检测bufferConsumeQueue中每个offset对应的物理偏移量的commitLog数据是否存在
            if (bufferConsumeQueue != null) {
                try {
                    // 预先设置状态未NO_MATCHED_MESSAGE
                    status = GetMessageStatus.NO_MATCHED_MESSAGE;

                    long nextPhyFileStartOffset = Long.MIN_VALUE;
                    long maxPhyOffsetPulling = 0;

                    int i = 0;
                    // 最多需要校验的消息条数
                    final int maxFilterMessageCount = Math.max(16000, maxMsgNums * ConsumeQueue.CQ_STORE_UNIT_SIZE);
                    // 是否记录消费落后磁盘量
                    final boolean diskFallRecorded = this.messageStoreConfig.isDiskFallRecorded();
                    ConsumeQueueExt.CqExtUnit cqExtUnit = new ConsumeQueueExt.CqExtUnit();
                    for (; i < bufferConsumeQueue.getSize() && i < maxFilterMessageCount; i += ConsumeQueue.CQ_STORE_UNIT_SIZE) {
                        // 读取第1-8个字节为物理偏移量offsetPy
                        long offsetPy = bufferConsumeQueue.getByteBuffer().getLong();
                        // 第9-12个字节为消息大小sizePy
                        int sizePy = bufferConsumeQueue.getByteBuffer().getInt();
                        // 第13-16个字节为tagsCode
                        long tagsCode = bufferConsumeQueue.getByteBuffer().getLong();

                        maxPhyOffsetPulling = offsetPy;
                        // 表示上一轮的消息,未在commitLog获取到
                        if (nextPhyFileStartOffset != Long.MIN_VALUE) {
                            // 如果下一个消息起始offset大于当前的要获取的offsetPy
                            if (offsetPy < nextPhyFileStartOffset)
                                continue;
                        }
                        // 通过最大偏移量-当前数据偏移量和内存大小作比较
                        // 判断数据是否可以从内存获取
                        boolean isInDisk = checkInDiskByCommitOffset(offsetPy, maxOffsetPy);
                        // 检查拉取的消息总大小是否到达上限,如果达到则中止这次消息拉取
                        if (this.isTheBatchFull(sizePy, maxMsgNums, getResult.getBufferTotalSize(), getResult.getMessageCount(),
                            isInDisk)) {
                            break;
                        }

                        boolean extRet = false, isTagsCodeLegal = true;
                        // 判断tagsCode是否小于等于0
                        if (consumeQueue.isExtAddr(tagsCode)) {
                            // 读取下一条消息,保存到cqExtUnit中
                            extRet = consumeQueue.getExt(tagsCode, cqExtUnit);
                            if (extRet) {
                                tagsCode = cqExtUnit.getTagsCode();
                            } else {
                                // can't find ext content.Client will filter messages by tag also.
                                log.error("[BUG] can't find consume queue extend file content!addr={}, offsetPy={}, sizePy={}, topic={}, group={}",
                                    tagsCode, offsetPy, sizePy, topic, group);
                                isTagsCodeLegal = false;
                            }
                        }
                        // 消息过滤,如果匹配不成功并且消息为空,暂时设置状态为NO_MATCHED_MESSAGE
                        // 匹配成功有以下几种情况
                        // SubscriptionData对象为空;
                        // SubscriptionData.classFilterMode变量为true;
                        // SubscriptionData对象的subString变量等于*;
                        // SubscriptionData对象的codeSet集合包含tagsCode值;
                        if (messageFilter != null
                            && !messageFilter.isMatchedByConsumeQueue(isTagsCodeLegal ? tagsCode : null, extRet ? cqExtUnit : null)) {
                            if (getResult.getBufferTotalSize() == 0) {
                                status = GetMessageStatus.NO_MATCHED_MESSAGE;
                            }

                            continue;
                        }
                        // 从 CommitLog 获取真实消息
                        // 先找到对应的mappedFile,然后根据offsetPy计算起始位置,获取sizePy长度的位置
                        SelectMappedBufferResult selectResult = this.commitLog.getMessage(offsetPy, sizePy);
                        if (null == selectResult) {
                            if (getResult.getBufferTotalSize() == 0) {
                                status = GetMessageStatus.MESSAGE_WAS_REMOVING;
                            }
                            // 未读取到消息,表示对应的mappedFile已经删除,从下一个文件的起始位置开始读取消息
                            nextPhyFileStartOffset = this.commitLog.rollNextFile(offsetPy);
                            continue;
                        }
                        // 过滤真实的消息
                        if (messageFilter != null
                            && !messageFilter.isMatchedByCommitLog(selectResult.getByteBuffer().slice(), null)) {
                            if (getResult.getBufferTotalSize() == 0) {
                                status = GetMessageStatus.NO_MATCHED_MESSAGE;
                            }
                            // release...
                            selectResult.release();
                            continue;
                        }
                        // 获取消息计数
                        this.storeStatsService.getGetMessageTransferedMsgCount().incrementAndGet();
                        // 保存拉取到的真实消息
                        getResult.addMessage(selectResult);
                        status = GetMessageStatus.FOUND;
                        nextPhyFileStartOffset = Long.MIN_VALUE;
                    }

                    if (diskFallRecorded) {
                        // 消费落后的数据量 = 最大offset - 此次消费的最大offset
                        long fallBehind = maxOffsetPy - maxPhyOffsetPulling;
                        // 记录消费落后的数据量
                        brokerStatsManager.recordDiskFallBehindSize(group, topic, queueId, fallBehind);
                    }
                    // 下一个开始的offset
                    nextBeginOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE);
                    long diff = maxOffsetPy - maxPhyOffsetPulling;
                    // 内存大小
                    long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE
                        * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0));
                    // 当消费进度落后量大于物理内存时,建议调换到从库去处理读
                    getResult.setSuggestPullingFromSlave(diff > memory);
                } finally {
                    bufferConsumeQueue.release();
                }
            } else {
                // 没找到消息
                status = GetMessageStatus.OFFSET_FOUND_NULL;
                // 设置从下一个mappedFile相同位置开始读取
                nextBeginOffset = nextOffsetCorrection(offset, consumeQueue.rollNextFile(offset));
                log.warn("consumer request topic: " + topic + "offset: " + offset + " minOffset: " + minOffset + " maxOffset: "
                    + maxOffset + ", but access logic queue failed.");
            }
        }
    } else {
        status = GetMessageStatus.NO_MATCHED_LOGIC_QUEUE;
        nextBeginOffset = nextOffsetCorrection(offset, 0);
    }
    // 统计拉取消息和未拉取到消息的次数
    if (GetMessageStatus.FOUND == status) {
        this.storeStatsService.getGetMessageTimesTotalFound().incrementAndGet();
    } else {
        this.storeStatsService.getGetMessageTimesTotalMiss().incrementAndGet();
    }
    long elapsedTime = this.getSystemClock().now() - beginTime;
    this.storeStatsService.setGetMessageEntireTimeMax(elapsedTime);

    getResult.setStatus(status);
    getResult.setNextBeginOffset(nextBeginOffset);
    getResult.setMaxOffset(maxOffset);
    getResult.setMinOffset(minOffset);
    return getResult;
}

这里就是通过offset先从consumerQueue获取到一批简化的数据,然后根据简化的数据再去commitLog中获取真实的消息。期间需要对每个消息进行过滤以及判断消息获取的数量是否到达了最大值
接下来回到PullMessageProcessor中,在上面拉取到消息后,就是消息的处理过程了

if (getMessageResult != null) {
    response.setRemark(getMessageResult.getStatus().name());
    responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset());
    responseHeader.setMinOffset(getMessageResult.getMinOffset());
    responseHeader.setMaxOffset(getMessageResult.getMaxOffset());
    // 如果建议从slave节点拉取数据
    if (getMessageResult.isSuggestPullingFromSlave()) {
        // 设置SuggestWhichBrokerId为1
        responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
    } else {
        responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
    }

    switch (this.brokerController.getMessageStoreConfig().getBrokerRole()) {
        case ASYNC_MASTER:
        case SYNC_MASTER:
            break;
        // 如果本机是slave角色
        case SLAVE:
            // 并且不允许从slave获取数据
            if (!this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
                response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
            }
            break;
    }
    // 设置建议读取消息的节点
    if (this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
        // 消费太慢,指向从节点
        if (getMessageResult.isSuggestPullingFromSlave()) {
            responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
        }
        // consume ok
        else {
            responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId());
        }
    } else {
        responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
    }

    switch (getMessageResult.getStatus()) {
        case FOUND:
            response.setCode(ResponseCode.SUCCESS);
            break;
        case MESSAGE_WAS_REMOVING:
            response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
            break;
        case NO_MATCHED_LOGIC_QUEUE:
        case NO_MESSAGE_IN_QUEUE:
            if (0 != requestHeader.getQueueOffset()) {
                response.setCode(ResponseCode.PULL_OFFSET_MOVED);

                // XXX: warn and notify me
                log.info("the broker store no queue data, fix the request offset {} to {}, Topic: {} QueueId: {} Consumer Group: {}",
                    requestHeader.getQueueOffset(),
                    getMessageResult.getNextBeginOffset(),
                    requestHeader.getTopic(),
                    requestHeader.getQueueId(),
                    requestHeader.getConsumerGroup()
                );
            } else {
                response.setCode(ResponseCode.PULL_NOT_FOUND);
            }
            break;
        case NO_MATCHED_MESSAGE:
            response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
            break;
        case OFFSET_FOUND_NULL:
            response.setCode(ResponseCode.PULL_NOT_FOUND);
            break;
        case OFFSET_OVERFLOW_BADLY:
            response.setCode(ResponseCode.PULL_OFFSET_MOVED);
            // XXX: warn and notify me
            log.info("the request offset: {} over flow badly, broker max offset: {}, consumer: {}",
                requestHeader.getQueueOffset(), getMessageResult.getMaxOffset(), channel.remoteAddress());
            break;
        case OFFSET_OVERFLOW_ONE:
            response.setCode(ResponseCode.PULL_NOT_FOUND);
            break;
        case OFFSET_TOO_SMALL:
            response.setCode(ResponseCode.PULL_OFFSET_MOVED);
            log.info("the request offset too small. group={}, topic={}, requestOffset={}, brokerMinOffset={}, clientIp={}",
                requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueOffset(),
                getMessageResult.getMinOffset(), channel.remoteAddress());
            break;
        default:
            assert false;
            break;
    }
    // 执行消费消息的hook函数
    if (this.hasConsumeMessageHook()) {
        ConsumeMessageContext context = new ConsumeMessageContext();
        context.setConsumerGroup(requestHeader.getConsumerGroup());
        context.setTopic(requestHeader.getTopic());
        context.setQueueId(requestHeader.getQueueId());

        String owner = request.getExtFields().get(BrokerStatsManager.COMMERCIAL_OWNER);

        switch (response.getCode()) {
            case ResponseCode.SUCCESS:
                int commercialBaseCount = brokerController.getBrokerConfig().getCommercialBaseCount();
                int incValue = getMessageResult.getMsgCount4Commercial() * commercialBaseCount;

                context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_SUCCESS);
                context.setCommercialRcvTimes(incValue);
                context.setCommercialRcvSize(getMessageResult.getBufferTotalSize());
                context.setCommercialOwner(owner);

                break;
            case ResponseCode.PULL_NOT_FOUND:
                if (!brokerAllowSuspend) {

                    context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS);
                    context.setCommercialRcvTimes(1);
                    context.setCommercialOwner(owner);

                }
                break;
            case ResponseCode.PULL_RETRY_IMMEDIATELY:
            case ResponseCode.PULL_OFFSET_MOVED:
                context.setCommercialRcvStats(BrokerStatsManager.StatsType.RCV_EPOLLS);
                context.setCommercialRcvTimes(1);
                context.setCommercialOwner(owner);
                break;
            default:
                assert false;
                break;
        }

        this.executeConsumeMessageHookBefore(context);
    }

    switch (response.getCode()) {
        // 请求成功
        case ResponseCode.SUCCESS:
            // 数据统计
            this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
                getMessageResult.getMessageCount());
            this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
                getMessageResult.getBufferTotalSize());
            this.brokerController.getBrokerStatsManager().incBrokerGetNums(getMessageResult.getMessageCount());
            // 从内存发送数据
            if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) {
                final long beginTimeMills = this.brokerController.getMessageStore().now();
                final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId());
                this.brokerController.getBrokerStatsManager().incGroupGetLatency(requestHeader.getConsumerGroup(),
                    requestHeader.getTopic(), requestHeader.getQueueId(),
                    (int) (this.brokerController.getMessageStore().now() - beginTimeMills));
                response.setBody(r);
            }
            // 从磁盘发送数据
            else {
                try {
                    FileRegion fileRegion =
                        new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()), getMessageResult);
                    channel.writeAndFlush(fileRegion).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            getMessageResult.release();
                            if (!future.isSuccess()) {
                                log.error("transfer many message by pagecache failed, {}", channel.remoteAddress(), future.cause());
                            }
                        }
                    });
                } catch (Throwable e) {
                    log.error("transfer many message by pagecache exception", e);
                    getMessageResult.release();
                }

                response = null;
            }
            break;
        case ResponseCode.PULL_NOT_FOUND:
            // hasSuspendFlag, 构建消息拉取时的拉取标记,默认为true
            if (brokerAllowSuspend && hasSuspendFlag) {
                // 取自 DefaultMQPullConsumer 的 brokerSuspendMaxTimeMillis属性
                long pollingTimeMills = suspendTimeoutMillisLong;
                // 如果不支持长轮询,则忽略 brokerSuspendMaxTimeMillis 属性,
                // 使用 shortPollingTimeMills,默认为1000ms作为下一次拉取消息的等待时间
                if (!this.brokerController.getBrokerConfig().isLongPollingEnable()) {
                    pollingTimeMills = this.brokerController.getBrokerConfig().getShortPollingTimeMills();
                }

                String topic = requestHeader.getTopic();
                long offset = requestHeader.getQueueOffset();
                int queueId = requestHeader.getQueueId();
                // 创建 PullRequest, 然后提交给 PullRequestHoldService 线程去调度,触发消息拉取
                PullRequest pullRequest = new PullRequest(request, channel, pollingTimeMills,
                    this.brokerController.getMessageStore().now(), offset, subscriptionData, messageFilter);
                // pullRequestHoldService 定时线程,最大延迟5秒判断是否有消息到达,然后执行消息的拉取
                this.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest);
                // 设置response=null,则此时此次调用不会向客户端输出任何字节,客户端网络请求客户端的读事件不会触发,不会触发对响应结果的处理,处于等待状态
                response = null;
                break;
            }

        case ResponseCode.PULL_RETRY_IMMEDIATELY:
            break;
        case ResponseCode.PULL_OFFSET_MOVED:
            if (this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE
                || this.brokerController.getMessageStoreConfig().isOffsetCheckInSlave()) {
                MessageQueue mq = new MessageQueue();
                mq.setTopic(requestHeader.getTopic());
                mq.setQueueId(requestHeader.getQueueId());
                mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName());

                OffsetMovedEvent event = new OffsetMovedEvent();
                event.setConsumerGroup(requestHeader.getConsumerGroup());
                event.setMessageQueue(mq);
                event.setOffsetRequest(requestHeader.getQueueOffset());
                event.setOffsetNew(getMessageResult.getNextBeginOffset());
                this.generateOffsetMovedEvent(event);
                log.warn(
                    "PULL_OFFSET_MOVED:correction offset. topic={}, groupId={}, requestOffset={}, newOffset={}, suggestBrokerId={}",
                    requestHeader.getTopic(), requestHeader.getConsumerGroup(), event.getOffsetRequest(), event.getOffsetNew(),
                    responseHeader.getSuggestWhichBrokerId());
            } else {
                responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId());
                response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                log.warn("PULL_OFFSET_MOVED:none correction. topic={}, groupId={}, requestOffset={}, suggestBrokerId={}",
                    requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getQueueOffset(),
                    responseHeader.getSuggestWhichBrokerId());
            }

            break;
        default:
            assert false;
    }
} else {
    response.setCode(ResponseCode.SYSTEM_ERROR);
    response.setRemark("store getMessage return null");
}

boolean storeOffsetEnable = brokerAllowSuspend;
storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag;
storeOffsetEnable = storeOffsetEnable
    && this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;
// 拉取消息时允许挂起 并且 拉取消息时主动提交offset 并且 当前节点是主节点
if (storeOffsetEnable) {
    // 提交offset,保存在内存中ConsumerOffsetManager.offsetTable中
    this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel),
        requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset());
}
return response;

代码比较简单,就不详细看了
值得注意的是response.getCode()=ResponseCode.PULL_NOT_FOUND时,关于长轮询的实现
这里继续看this.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest);

// 根据主题名称 + 队列id, 获取 ManyPullRequest, 对于同一个 topic + 队列的拉取请求用 ManyPullRequest包装,
// 然后将 pullRequest 添加到 ManyPullRequest 中
public void suspendPullRequest(final String topic, final int queueId, final PullRequest pullRequest) {
    String key = this.buildKey(topic, queueId);
    ManyPullRequest mpr = this.pullRequestTable.get(key);
    if (null == mpr) {
        mpr = new ManyPullRequest();
        ManyPullRequest prev = this.pullRequestTable.putIfAbsent(key, mpr);
        if (prev != null) {
            mpr = prev;
        }
    }

    mpr.addPullRequest(pullRequest);
}

这里就是使用一个ManyPullRequest对象,暂时保存这次的pullRequest。那么pullRequestTable是在什么时候触发的呢?
带着这个疑问,我们来看下PullRequestHoldService这个类,整个类中有两种方式触发了pullRequestTable中保存的请求的执行
首先是run方法

public void run() {
    log.info("{} service started", this.getServiceName());
    while (!this.isStopped()) {
        try {
            // 如果开启了长轮询模式,则每次只挂起 5s,然后就去尝试拉取。
            if (this.brokerController.getBrokerConfig().isLongPollingEnable()) {
                this.waitForRunning(5 * 1000);
            }
            // 如果不开启长轮询模式,则只挂起一次,挂起时间为 shortPollingTimeMills,然后去尝试查找消息
            else {
                this.waitForRunning(this.brokerController.getBrokerConfig().getShortPollingTimeMills());
            }

            long beginLockTimestamp = this.systemClock.now();
            this.checkHoldRequest();
            long costTime = this.systemClock.now() - beginLockTimestamp;
            if (costTime > 5 * 1000) {
                log.info("[NOTIFYME] check hold request cost {} ms.", costTime);
            }
        } catch (Throwable e) {
            log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }

    log.info("{} service end", this.getServiceName());
}

这里就是启动一个线程,经过默认的时间后就执行一次checkHoldRequest方法

private void checkHoldRequest() {
    for (String key : this.pullRequestTable.keySet()) {
        String[] kArray = key.split(TOPIC_QUEUEID_SEPARATOR);
        if (2 == kArray.length) {
            String topic = kArray[0];
            int queueId = Integer.parseInt(kArray[1]);
            // 根据主题,消费队列ID查找队列的最大偏移量。
            final long offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId);
            try {
                // 根据该offset,判断是否有新的消息达到。
                this.notifyMessageArriving(topic, queueId, offset);
            } catch (Throwable e) {
                log.error("check hold request failed. topic={}, queueId={}", topic, queueId, e);
            }
        }
    }
}

继续看notifyMessageArriving方法

public void notifyMessageArriving(final String topic, final int queueId, final long maxOffset) {
    notifyMessageArriving(topic, queueId, maxOffset, null, 0, null, null);
}

/**
 * 通知消息到达
 *
 * @param topic        主题名称
 * @param queueId      队列id
 * @param maxOffset    消费队列当前最大偏移量
 * @param tagsCode     消息tag hashcode,基于tag消息过滤
 * @param msgStoreTime 消息存储时间
 * @param filterBitMap 过滤位图
 * @param properties   消息属性,基于属性的消息过滤
 */
public void notifyMessageArriving(final String topic, final int queueId, final long maxOffset, final Long tagsCode,
    long msgStoreTime, byte[] filterBitMap, Map<String, String> properties) {
    String key = this.buildKey(topic, queueId);
    ManyPullRequest mpr = this.pullRequestTable.get(key);
    if (mpr != null) {
        // 获取主题与队列的所有 PullRequest 并清除内部 pullRequest 集合,避免重复拉取
        List<PullRequest> requestList = mpr.cloneListAndClear();
        if (requestList != null) {
            List<PullRequest> replayList = new ArrayList<PullRequest>();

            for (PullRequest request : requestList) {
                long newestOffset = maxOffset;
                // 如果待拉取偏移量(pullFromThisOffset)大于消息队列的最大有效偏移量,则再次获取消息队列的最大有效偏移量,再给一次机会
                if (newestOffset <= request.getPullFromThisOffset()) {
                    newestOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId);
                }
                // 如果队列最大偏移量大于 pullFromThisOffset 说明有新的消息到达,
                // 先简单对消息根据 tag,属性进行一次消息过滤,
                // 如果 tag,属性为空,则消息过滤器会返回true,然后 executeRequestWhenWakeup进行消息拉取,结束长轮询
                if (newestOffset > request.getPullFromThisOffset()) {
                    boolean match = request.getMessageFilter().isMatchedByConsumeQueue(tagsCode,
                        new ConsumeQueueExt.CqExtUnit(tagsCode, msgStoreTime, filterBitMap));
                    // match by bit map, need eval again when properties is not null.
                    if (match && properties != null) {
                        match = request.getMessageFilter().isMatchedByCommitLog(null, properties);
                    }

                    if (match) {
                        try {
                            this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(),
                                request.getRequestCommand());
                        } catch (Throwable e) {
                            log.error("execute request when wakeup failed.", e);
                        }
                        continue;
                    }
                }
                // 如果挂起时间超过 suspendTimeoutMillisLong,则超时,结束长轮询,调用executeRequestWhenWakeup 进行消息拉取,并返回结果到客户端
                if (System.currentTimeMillis() >= (request.getSuspendTimestamp() + request.getTimeoutMillis())) {
                    try {
                        this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(),
                            request.getRequestCommand());
                    } catch (Throwable e) {
                        log.error("execute request when wakeup failed.", e);
                    }
                    continue;
                }

                replayList.add(request);
            }

            if (!replayList.isEmpty()) {
                // 如果待拉取偏移量大于消息消费队列最大偏移量,并且未超时,调用 mpr.addPullRequest(replayList) 将拉取任务重新放入,待下一次检测
                mpr.addPullRequest(replayList);
            }
        }
    }
}

这里就是用当前consumerQueue队列的最大offset和请求中的开始拉取offset做比较,如果大于,代表有新的消息进入队列,则执行请求。或者判断是否到了最大的挂起时间,如果到达了,也执行请求。否则就将PullRequest放回ManyPullRequest中,那么PullRequestHoldService是在何时启动的呢?
在broker的启动过程中曾经提到过一段代码

// 消息拉取相关,后续分析
if (this.pullRequestHoldService != null) {
    this.pullRequestHoldService.start();
}

还有另一种方式执行notifyMessageArriving方法,也就是NotifyMessageArrivingListener

public class NotifyMessageArrivingListener implements MessageArrivingListener {
    private final PullRequestHoldService pullRequestHoldService;

    public NotifyMessageArrivingListener(final PullRequestHoldService pullRequestHoldService) {
        this.pullRequestHoldService = pullRequestHoldService;
    }

    @Override
    public void arriving(String topic, int queueId, long logicOffset, long tagsCode,
        long msgStoreTime, byte[] filterBitMap, Map<String, String> properties) {
        this.pullRequestHoldService.notifyMessageArriving(topic, queueId, logicOffset, tagsCode,
            msgStoreTime, filterBitMap, properties);
    }
}

那么NotifyMessageArrivingListener是在什么时候被通知的呢?
在broker的启动过程中曾经讲到过ReputMessageService.doReput()方法,其中有一段代码是


doReput

也就是说当provider消息推送到broker后,broker会通知NotifyMessageArrivingListener消息到达,从而及时的结束挂起的轮询的链接。
到此消息拉取broker端的流程就结束了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容