RocketMQ源码学习 (八) PullMessageProcessor处理器源码学习

1 processRequest方法

1.1 processRequest方法的整体流程

上文中我们学习了关于PullMessageProcessor处理器的原理,这一篇开始探究其源码的构成,拉消息 的主体逻辑如下:

    /**
     * @param channel 服务器与客户端netty通道
     * @param request 客户端请求(RemotingCommand)
     * @param brokerAllowSuspend 是否允许服务器端长轮询
     */
    private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend)
        throws RemotingCommandException {

        // 创建服务器端对本请求的“响应对象”,注意:header 类型 PullMessageResponseHeader
        RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class);

        // 获取response对象的 header
        final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader();


        // 从request请求中,解析出“requestHeader”对象,类型是:PullMessageRequestHeader
        final PullMessageRequestHeader requestHeader =
            (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class);

        // 设置响应对象的opaque 为 request请求的 opaque,为什么要设置?
        // 客户端需要根据 response  opaque 找到 ResponseFuture 然后完成后面的事情。
        response.setOpaque(request.getOpaque());

        log.debug("receive PullMessage request command, {}", request);

        //接下来的代码就是各种校验
        /**
         * 校验服务器端是否可读
         */
        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;
        }

        /**
         * 获取消费者组配置信息,如果为 null,则也是直接返回
         */
        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;
        }

        // 是否允许服务器端长轮询(true)
        final boolean hasSuspendFlag = PullSysFlag.hasSuspendFlag(requestHeader.getSysFlag());
        // 客户端是否提交 offset(true)
        final boolean hasCommitOffsetFlag = PullSysFlag.hasCommitOffsetFlag(requestHeader.getSysFlag());
        // 客户端请求是否包含 订阅数据(false)
        final boolean hasSubscriptionFlag = PullSysFlag.hasSubscriptionFlag(requestHeader.getSysFlag());
        //长轮询允许的时间长度  15秒
        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;
        }

        //topic权限是否可读
        if (!PermName.isReadable(topicConfig.getPerm())) {
            response.setCode(ResponseCode.NO_PERMISSION);
            response.setRemark("the topic[" + requestHeader.getTopic() + "] pulling message is forbidden");
            return response;
        }


        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;
        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 {
            //消费者组信息是否为空
            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 messageFilter;
        if (this.brokerController.getBrokerConfig().isFilterSupportRetry()) {
            messageFilter = new ExpressionForRetryMessageFilter(subscriptionData, consumerFilterData,
                this.brokerController.getConsumerFilterManager());
        } else {
            messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData,
                this.brokerController.getConsumerFilterManager());
        }

        // 查询消息的核心入口
        // 参数1:consumerGroup 消费者组
        // 参数2:topic 主题
        // 参数3:queueId
        // 参数4:queueOffset
        // 参数5:maxMsgNums 本次查询最多可拿的消息数量
        // 参数6:messageFilter 上一步创建的消息过滤器(服务器这里,一般都是tagCode过滤..,getMessage方法通过consumerQueue拿到ConsumerQueueData,
        //      该对象有第三个字段tagCode,和messageFilter做个匹配,如果匹配到则去commitLog里拿到这条数据,如果匹配返回false,直接跳过这条消息,不再去
        //      commitL里查询了)
        final GetMessageResult getMessageResult =
            this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
                requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);


        if (getMessageResult != null) {
            response.setRemark(getMessageResult.getStatus().name());
            // 设置responseHeader.nextBeginOffset 为 服务器计算出来的 该队列 下一次pull时使用的offset,假如拉消息之前的offset是10,
            //      拉取了5个之后的offset就是15
            responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset());
            // 设置responseHeader.minOffset 为 pull queue的最小offset
            responseHeader.setMinOffset(getMessageResult.getMinOffset());
            // 设置responseHeader.maxOffset 为 pull queue的最大offset
            responseHeader.setMaxOffset(getMessageResult.getMaxOffset());

            // 设置客户端下次拉该queue时推荐使用的 brokerId
            if (getMessageResult.isSuggestPullingFromSlave()) {
                responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
            } else {
                responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
            }

            // 这里代码的逻辑:如果当前主机节点角色为 slave 并且 从节点读 并未开启的话,直接给客户端 一个状态“PULL_RETRY_IMMEDIATELY”
            // 客户端 检查是该状态后,会重新立马再次发起pull,此时使用的brokerId 为 MASTER_ID 了。
            switch (this.brokerController.getMessageStoreConfig().getBrokerRole()) {
                case ASYNC_MASTER:
                case SYNC_MASTER:
                    break;
                case SLAVE:
                    if (!this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
                        response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                        responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
                    }
                    break;
            }
            if (this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
                // consume too slow ,redirect to another machine
                if (getMessageResult.isSuggestPullingFromSlave()) {
                    responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
                }
                // consume ok
                else {
                    responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId());
                }
            } else {
                responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
            }
            
            // 根据getMessageResult的状态 设置 response的code ,,参考上一篇的状态码原理图,这块代码很混乱.
            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,这里这个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();

                        // 本次pull出来的全部消息  byte数组 表示
                        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));

                        // 将 消息 byte数组 保存到 response body字段
                        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:
                    // 大部分情况下都是 pullRequest.offset  ==  queue.maxOffset(客户端消费进度和服务器消息进度持平了)
                    // queue maxOffset 计算方式: dataSize / unit_size(20) => 得出来的结果
                    // 举例子,当dataSize 内存储一条 CQData 时, dataSize = 20
                    // dataSize / unit_size(20) => 1
                    // 当客户端 offset == 1 时,来到服务器拉消息,getMessage(..)方法 首先查询 CQ 数据,
                    // offset * unit_size => 20 这个物理位点
                    // 到该位点 查询数据,肯定得到 null,因为CQ的有效数据范围 [0,20]
                    // 所以,这种情况下 状态为 :PULL_NOT_FOUND
                    // 这里需要进行长轮询,不然的话,直接返回给客户端,客户单那边逻辑 会再次立马发起 pull 请求,
                    // 这样会频繁拉取服务器,导致 服务器 压力很大...最好的办法就是长轮询。

                    // 条件成立:说明本次请求允许长轮询
                    // (注意brokerAllowSuspend,它是由重载的方法 传的 true,当长轮询结束时 再次 执行 processRequest的时候,该参数传的是 false)
                    // 也就是每次pull请求,至多在服务器端 长轮询 控制一次
                    if (brokerAllowSuspend && hasSuspendFlag) {
                        // 获取长轮询时间 15000 毫秒
                        long pollingTimeMills = suspendTimeoutMillisLong;
                        if (!this.brokerController.getBrokerConfig().isLongPollingEnable()) {
                            pollingTimeMills = this.brokerController.getBrokerConfig().getShortPollingTimeMills();
                        }

                        // 拉消息请求的“主题”
                        String topic = requestHeader.getTopic();
                        // 拉消息请求的“偏移量”
                        long offset = requestHeader.getQueueOffset();
                        // 拉消息请求的“队列ID”
                        int queueId = requestHeader.getQueueId();

                        // 创建 长轮询 pullRequest 对象
                        PullRequest pullRequest = new PullRequest(request, channel, pollingTimeMills,
                            this.brokerController.getMessageStore().now(), offset, subscriptionData, messageFilter);

                        // 将“长轮询pullRequest”对象 交给 长轮询服务
                        this.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest);

                        // 将response 设置为了 null ,设置成null之后,外层 requestTask 拿到的结果就是 null,
                        // 它是null的话,requestTask 内部的callBack 就不会给客户端发送任何数据了..
                        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");
        }


        // 1. brokerAllowSuspend == true
        // 2. sysFlag 表示提交消费者本地该queue的offset
        // 3. 当前broker节点角色为 master 节点
        // 三个条件全部成立时,才在broker端存储该消费者组内该queue的消费进度
        boolean storeOffsetEnable = brokerAllowSuspend;
        storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag;
        storeOffsetEnable = storeOffsetEnable
            && this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;

        if (storeOffsetEnable) {
            // 存储该消费者组下该主题的指定队列的消费进度
            this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel),
                requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset());
        }
        // 返回response,当response不为null时,外层requestTask的callback 会将数据写给客户端
        return response;
    }

1.2 GetMessageResult对象主要构成

public class GetMessageResult {
    // 查询消息时,最底层都是 mappedFile 支持的查询,它查询时返回给外层一个 SelectMappedBufferResult,
    // mappedFile每查询一次 都会 refCount ++ ,通过SelectMappedBufferResult持有mappedFile,完成资源释放的句柄。
    private final List<SelectMappedBufferResult> messageMapedList =
        new ArrayList<SelectMappedBufferResult>(100);
    // 该List内存储消息,每一条消息都被转成 ByteBuffer 表示了
    private final List<ByteBuffer> messageBufferList = new ArrayList<ByteBuffer>(100);
    // 查询结果状态  FOUND、NO_MATCHED_MESSAGE,MESSAGE_WAS_REMOVING,OFFSET_FOUND_NULL,OFFSET_OVERFLOW_BADLY......
    private GetMessageStatus status;
    // 客户端下次再向当前Queue拉消息时,使用的 offset
    private long nextBeginOffset;
    // 当前queue最小offset
    private long minOffset;
    // 当前queue最大offset
    private long maxOffset;
    // 消息总byte大小
    private int bufferTotalSize = 0;
    // 服务器建议客户端下次到该queue 拉消息时 使用 主/从 节点
    private boolean suggestPullingFromSlave = false;
}

1.3 PullRequest对象


public class PullRequest {
    // 客户端请求RemotingCommand对象
    private final RemotingCommand requestCommand;
    // 服务器和客户端的会话Channel
    private final Channel clientChannel;
    // 长轮询超时限制 15 秒
    private final long timeoutMillis;
    // 长轮询开始时间
    private final long suspendTimestamp;
    // requestCommand header 提取出来的 本次 pull 队列的 offset
    private final long pullFromThisOffset;
    // 该主题的订阅数据
    private final SubscriptionData subscriptionData;
    // 消息过滤器,一般都是 tagCode过滤
    private final MessageFilter messageFilter;

    public PullRequest(RemotingCommand requestCommand, Channel clientChannel, long timeoutMillis, long suspendTimestamp,
        long pullFromThisOffset, SubscriptionData subscriptionData,
        MessageFilter messageFilter) {
        this.requestCommand = requestCommand;
        this.clientChannel = clientChannel;
        // 当前系统时间
        this.timeoutMillis = timeoutMillis;
        this.suspendTimestamp = suspendTimestamp;
        this.pullFromThisOffset = pullFromThisOffset;
        this.subscriptionData = subscriptionData;
        this.messageFilter = messageFilter;
    }
    //......省略
}

2. 长轮询相关源码学习

长轮询服务的相关逻辑在PullRequestHoldService中,接下来主要看PullRequestHoldService的run方法

2.1 run方法的实现


    @Override
    public void run() {
        log.info("{} service started", this.getServiceName());
        while (!this.isStopped()) {
            try {
                if (this.brokerController.getBrokerConfig().isLongPollingEnable()) {
                    // 服务器开启长轮询开关:每次循环休眠5秒
                    this.waitForRunning(5 * 1000);
                } else {
                    // 服务器关闭长轮询开关:每次循环休眠1秒
                    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());
    }

run方法中,核心方法为checkHoldRequest方法没接下来看该方法的实现。

2.2 checkHoldRequest方法的实现

    private void checkHoldRequest() {
        for (String key : this.pullRequestTable.keySet()) {
            // 循环体内为每个 topic@queueId k-v 的处理逻辑

            // key 按照@拆分,得到 topic 和 queueId
            String[] kArray = key.split(TOPIC_QUEUEID_SEPARATOR);

            if (2 == kArray.length) {
                // 主题
                String topic = kArray[0];
                // queueId
                int queueId = Integer.parseInt(kArray[1]);
                //到存储模块查询该ConsumeQueue的最大offset 如何获取?获取ConsumeQueue中MappedFile最后一个的文件名 + 文件正在顺序写的数据位点 =》 maxOffset
                final long offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId);
                try {
                    // 通知消息达到的逻辑
                    // 参数1:主题
                    // 参数2:queueId
                    // 参数3:offset,当前queue最大offset
                    this.notifyMessageArriving(topic, queueId, offset);
                } catch (Throwable e) {
                    log.error("check hold request failed. topic={}, queueId={}", topic, queueId, e);
                }
            }
        }
    }

2.3 notifyMessageArriving方法实现

notifyMessageArriving方法为通知消息达到的逻辑。

    // 通知消息达到的逻辑
    // 参数1:主题
    // 参数2:queueId
    // 参数3:offset,当前queue最大offset
    public void notifyMessageArriving(final String topic, final int queueId, final long maxOffset) {
        notifyMessageArriving(topic, queueId, maxOffset, null, 0, null, null);
    }
    // 该方法有两个调用点:
    // 1. pullRequestHoldService.run()..
    // 2. ReputMessageService 异步构建 ConsumeQueue 和 index 的消息转发服务
    public void notifyMessageArriving(final String topic, final int queueId, final long maxOffset, final Long tagsCode,
        long msgStoreTime, byte[] filterBitMap, Map<String, String> properties) {
        // 构建key,规则:主题@queueId
        String key = this.buildKey(topic, queueId);
        // 获取“主题@queueId”的manyPullRequest对象
        ManyPullRequest mpr = this.pullRequestTable.get(key);
        if (mpr != null) {
            // 获取该queue下的 pullRequest list 数据
            List<PullRequest> requestList = mpr.cloneListAndClear();
            if (requestList != null) {
                // 重放列表,当某个 pullRequest 即不超时,
                // 对应的queue的maxOffset <= pullRequest.offset 的话,就将该pullRequest 再放入replayList
                List<PullRequest> replayList = new ArrayList<PullRequest>();
                for (PullRequest request : requestList) {
                    long newestOffset = maxOffset;
                    if (newestOffset <= request.getPullFromThisOffset()) {
                        // 保证newestOffset为 queue的 maxOffset
                        newestOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId);
                    }
                    // 条件成立:说明该request关注的queue内有 本次pull 查询的数据了,长轮询该结束了
                    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 {
                                // 将满足条件的 pullRequest 再次封装出 RequestTask 提交到 线程池内执行,
                                // 会再次调用 PullMessageProcess.processRequest(...) 三个参数的方法,并且 不允许再次长轮询。                 this.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(),
                                    request.getRequestCommand());
                            } catch (Throwable e) {
                                log.error("execute request when wakeup failed.", e);
                            }
                            continue;
                        }
                    }
                    // 判断 该 pullRequest 是否超时,超时,也是将它再次封装出 RequestTask 提交到 线程池内执行   三个参数的方法,并且 不允许再次长轮询。
                    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);
                }
            }
        }
    }

2.4 executeRequestWhenWakeup方法

在该方法内,不会再次触发长轮询,将数据发送给客户端就可以

    public void executeRequestWhenWakeup(final Channel channel,
        final RemotingCommand request) throws RemotingCommandException {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    // 执行 processRequest 方法,注意:第三个参数 这里是 false了,不会再次触发 长轮询了。
                    final RemotingCommand response = PullMessageProcessor.this.processRequest(channel, request, false);

                    if (response != null) {
                        response.setOpaque(request.getOpaque());
                        response.markResponseType();
                        try {
                            // 将结果数据发送给客户端
                            channel.writeAndFlush(response).addListener(new ChannelFutureListener() {
                                @Override
                                public void operationComplete(ChannelFuture future) throws Exception {
                                    if (!future.isSuccess()) {
                                        log.error("processRequestWrapper response to {} failed",
                                            future.channel().remoteAddress(), future.cause());
                                        log.error(request.toString());
                                        log.error(response.toString());
                                    }
                                }
                            });
                        } catch (Throwable e) {
                            log.error("processRequestWrapper process request over, but response failed", e);
                            log.error(request.toString());
                            log.error(response.toString());
                        }
                    }
                } catch (RemotingCommandException e1) {
                    log.error("excuteRequestWhenWakeup run", e1);
                }
            }
        };
        this.brokerController.getPullMessageExecutor().submit(new RequestTask(run, channel, request));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值