ROCKETMQ消息消费原理

本文基于并发消费逻辑,介绍RocketMQ消息消费过程。正常消费时,broker端过滤消息后通知客户端拉取,客户端从阻塞队列取通知拉消息,拉取前后会做校验,然后异步消费。还分析了Client端主动查询和消息直接消费场景的源码。

摘要

下面的阅读顺序是基于并发消费的逻辑来看的。正常消费逻辑是broker端收到消息之后,经过tag过滤,通知订阅的客户端来拉取消息。这个通知暂时被存放在客户端的阻塞队列中。客户端一直循环从这个队列中取通知,根据通知去拉消息。本地拉消息之前会校验本地的消息堆积情况(不找过1000个,大小不超过100M),拉过来之后校验tag过滤情况(这是在broker端已经过滤一次之后,再次做的过滤,一般情况broker端已经过滤掉了,拉取通知不会发过来的),然后异步消费。

消息消费逻辑序列图

下图是引用《ROCKETMQ技术内幕》上的一张图

源码阅读

Client端主动发起查询部分源码阅读

PullMessageService

org.apache.rocketmq.client.impl.consumer.PullMessageService

       @Override
    public void run() {
        log.info(this.getServiceName() + " service started");

        while (!this.isStopped()) {
            try {
            	// 从阻塞队列拿 PullRequest
                PullRequest pullRequest = this.pullRequestQueue.take();
                // 拉取消息
                this.pullMessage(pullRequest);
            } catch (InterruptedException ignored) {
            } catch (Exception e) {
                log.error("Pull Message Service Run Method exception", e);
            }
        }

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

PullMessageService.pullMessage

org.apache.rocketmq.client.impl.consumer.PullMessageService.pullMessage(PullRequest)

    //也是启动 DefaultMQPushConsumerImpl 发送一个 pullRequest 请求
    private void pullMessage(final PullRequest pullRequest) {
        final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
        if (consumer != null) {
            DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
            impl.pullMessage(pullRequest);
        } else {
            log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);
        }
    }

DefaultMQPushConsumerImpl.pullMessage

org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl.pullMessage(PullRequest)

public void pullMessage(final PullRequest pullRequest) {
        final ProcessQueue processQueue = pullRequest.getProcessQueue();
        if (processQueue.isDropped()) {//很多没用的消息在这里丢弃掉了
            log.info("the pull request[{}] is dropped.", pullRequest.toString());
            return;
        }

       ……
        // 本地缓存的消息不能超过1000个
        ……
        //本地缓存的消息不能超过100M
        ……

        if (!this.consumeOrderly) {
            if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) {
                this.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
                if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) {
                    log.warn(
                        "the queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, pullRequest={}, flowControlTimes={}",
                        processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(),
                        pullRequest, queueMaxSpanFlowControlTimes);
                }
                return;
            }
        } else {
            if (processQueue.isLocked()) {
                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, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
                log.info("pull message later because not locked in broker, {}", pullRequest);
                return;
            }
        }
        // 根据topic 查找订阅关系
       ……

        final long beginTimestamp = System.currentTimeMillis();

        /**
         * 拉取消息结束之后会调用这个方法处理拉取到的消息
         * org.apache.rocketmq.client.impl.MQClientAPIImpl.pullMessageAsync(String, RemotingCommand, long, PullCallback)
         */
        PullCallback pullCallback = new PullCallback() {
            @Override
            public void onSuccess(PullResult pullResult) {
                if (pullResult != null) {
                    pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
                        subscriptionData);//这里对订阅关系里的tag进行过滤

                    switch (pullResult.getPullStatus()) {
                        case FOUND:
                        	// 当前消息的offset
                            long prevRequestOffset = pullRequest.getNextOffset();
                            //下一次要啦的消息offset
                            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 {
                                firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset();

                                DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(),
                                    pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size());
                                // 将拉取的消息存放在processQueue中
                                boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
                                /**
                                 * 将processQueue丢给consumeMessageService(从而让拉取的消息进行消费)
                                 * 当前消息在这里开始消费,重要
                                 */
                                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 {
                                    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:
                            …… 后面几个省略了
                    }
                }
            }

            @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, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
            }
        };

        boolean commitOffsetEnable = false;
        long commitOffsetValue = 0L;
        if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
            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();
        }

        int sysFlag = PullSysFlag.buildSysFlag(
            commitOffsetEnable, // commitOffset
            true, // suspend
            subExpression != null, // subscription
            classFilter // class filter
        );
        try {
            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, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);
        }
    }

PullAPIWrapper.pullKernelImpl

org.apache.rocketmq.client.impl.consumer.PullAPIWrapper.pullKernelImpl(MessageQueue, String, String, long, long, int, int, long, long, long, CommunicationMode, 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 {
        FindBrokerResult findBrokerResult =
            this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                this.recalculatePullFromWhichNode(mq), false);
        if (null == findBrokerResult) {
            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()) {
                    throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
                        + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
                }
            }
            int sysFlagInner = sysFlag;

            if (findBrokerResult.isSlave()) {
                sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
            }

            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);

            String brokerAddr = findBrokerResult.getBrokerAddr();
            if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
                brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);
            }

            PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
                brokerAddr,
                requestHeader,
                timeoutMillis,
                communicationMode,
                pullCallback);

            return pullResult;
        }

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

MQClientAPIImpl.pullMessage

org.apache.rocketmq.client.impl.MQClientAPIImpl.pullMessage(String, PullMessageRequestHeader, long, CommunicationMode, PullCallback)

//远程拉取message
    public PullResult pullMessage(
        final String addr,
        final PullMessageRequestHeader requestHeader,
        final long timeoutMillis,
        final CommunicationMode communicationMode,
        final PullCallback pullCallback
    ) throws RemotingException, MQBrokerException, InterruptedException {
        RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);

        switch (communicationMode) {
            case ONEWAY:
                assert false;
                return null;
            case ASYNC:// 正常消息拉取流程走的是这个异步的流程
                this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
                return null;
            case SYNC:
                return this.pullMessageSync(addr, request, timeoutMillis);
            default:
                assert false;
                break;
        }

        return null;
    }

MQClientAPIImpl.pullMessageAsync

org.apache.rocketmq.client.impl.MQClientAPIImpl.pullMessageAsync(String, RemotingCommand, long, PullCallback)

 private void pullMessageAsync(
        final String addr,
        final RemotingCommand request,
        final long timeoutMillis,
        final PullCallback pullCallback
    ) throws RemotingException, InterruptedException {
        this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
            @Override
            public void operationComplete(ResponseFuture responseFuture) {
                RemotingCommand response = responseFuture.getResponseCommand();
                if (response != null) {
                    try {
                        PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response);

                        assert pullResult != null;
                        pullCallback.onSuccess(pullResult);// 这里回调了  pullCallback
                    } 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()));
                    }
                }
            }
        });
    }

submitConsumeRequest

远程拉取消息之后,执行PullCallback的方法,里面将消息封装成ConsumeRequest,之后提交给线程池,异步执行


  /**
     * 消息在这里开始消费
     */
    @Override
    public void submitConsumeRequest(
        final List<MessageExt> msgs,
        final ProcessQueue processQueue,
        final MessageQueue messageQueue,
        final boolean dispatchToConsume) {
        final int consumeBatchSize = this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();
        if (msgs.size() <= consumeBatchSize) {
        	//封装成一个 ConsumeRequest,下面提交到线程池异步消费
            ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue);
            try {
            	//提交到线程池异步消费
                this.consumeExecutor.submit(consumeRequest);
            } catch (RejectedExecutionException e) {
                this.submitConsumeRequestLater(consumeRequest);
            }
        } else {
            for (int total = 0; total < msgs.size(); ) {
                List<MessageExt> msgThis = new ArrayList<MessageExt>(consumeBatchSize);
                for (int i = 0; i < consumeBatchSize; i++, total++) {
                    if (total < msgs.size()) {
                        msgThis.add(msgs.get(total));
                    } else {
                        break;
                    }
                }

                ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue);
                try {
                    this.consumeExecutor.submit(consumeRequest);
                } catch (RejectedExecutionException e) {
                    for (; total < msgs.size(); total++) {
                        msgThis.add(msgs.get(total));
                    }

                    this.submitConsumeRequestLater(consumeRequest);
                }
            }
        }
    }

真正执行消息消费

 @Override
        public void run() {
            if (this.processQueue.isDropped()) {
                log.info("the message queue not be able to consume, because it's dropped. group={} {}", ConsumeMessageConcurrentlyService.this.consumerGroup, this.messageQueue);
                return;
            }

            MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener;
            ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
            ConsumeConcurrentlyStatus status = null;

            ConsumeMessageContext consumeMessageContext = null;
            if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                consumeMessageContext = new ConsumeMessageContext();
                consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup());
                consumeMessageContext.setProps(new HashMap<String, String>());
                consumeMessageContext.setMq(messageQueue);
                consumeMessageContext.setMsgList(msgs);
                consumeMessageContext.setSuccess(false);
                ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
            }

            long beginTimestamp = System.currentTimeMillis();
            boolean hasException = false;
            ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
            try {
                ConsumeMessageConcurrentlyService.this.resetRetryTopic(msgs);
                if (msgs != null && !msgs.isEmpty()) {
                    for (MessageExt msg : msgs) {
                        MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis()));
                    }
                }
                status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
            } catch (Throwable e) {
                log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}",
                    RemotingHelper.exceptionSimpleDesc(e),
                    ConsumeMessageConcurrentlyService.this.consumerGroup,
                    msgs,
                    messageQueue);
                hasException = true;
            }
            long consumeRT = System.currentTimeMillis() - beginTimestamp;
            if (null == status) {
                if (hasException) {
                    returnType = ConsumeReturnType.EXCEPTION;
                } else {
                    returnType = ConsumeReturnType.RETURNNULL;
                }
            } else if (consumeRT >= defaultMQPushConsumer.getConsumeTimeout() * 60 * 1000) {
                returnType = ConsumeReturnType.TIME_OUT;
            } else if (ConsumeConcurrentlyStatus.RECONSUME_LATER == status) {
                returnType = ConsumeReturnType.FAILED;
            } else if (ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status) {
                returnType = ConsumeReturnType.SUCCESS;
            }

            if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name());
            }

            if (null == status) {
                log.warn("consumeMessage return null, Group: {} Msgs: {} MQ: {}",
                    ConsumeMessageConcurrentlyService.this.consumerGroup,
                    msgs,
                    messageQueue);
                status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }

            if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
                consumeMessageContext.setStatus(status.toString());
                consumeMessageContext.setSuccess(ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status);
                ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);
            }

            ConsumeMessageConcurrentlyService.this.getConsumerStatsManager()
                .incConsumeRT(ConsumeMessageConcurrentlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT);

            if (!processQueue.isDropped()) {
                ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this);
            } else {
                log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs);
            }
        }

消息直接消费场景

从控制台发起resend消息,就是这种消费场景。比较简单,不经过tag过滤。

ClientRemotingProcessor.processRequest

org.apache.rocketmq.client.impl.ClientRemotingProcessor.processRequest(ChannelHandlerContext, RemotingCommand)

 @Override
    public RemotingCommand processRequest(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        switch (request.getCode()) {
            case RequestCode.CHECK_TRANSACTION_STATE:// 处理broker发送过来的事务回查
                return this.checkTransactionState(ctx, request);
            case RequestCode.NOTIFY_CONSUMER_IDS_CHANGED://正常消息接收
                return this.notifyConsumerIdsChanged(ctx, request);
            case RequestCode.RESET_CONSUMER_CLIENT_OFFSET:
                return this.resetOffset(ctx, request);//重置消费offset的
            case RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT:
                return this.getConsumeStatus(ctx, request);

            case RequestCode.GET_CONSUMER_RUNNING_INFO:
                return this.getConsumerRunningInfo(ctx, request);

            case RequestCode.CONSUME_MESSAGE_DIRECTLY:
            	/**
            	 * 控制台发消息消息走这里
            	 * 直接调用listener消息
            	 * 控制台直接发送消息
            	 */
                return this.consumeMessageDirectly(ctx, request);
            default:
                break;
        }
        return null;
    }

MQClientInstance.consumeMessageDirectly

org.apache.rocketmq.client.impl.factory.MQClientInstance.consumeMessageDirectly(MessageExt, String, String)

    private RemotingCommand consumeMessageDirectly(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final ConsumeMessageDirectlyResultRequestHeader requestHeader =
            (ConsumeMessageDirectlyResultRequestHeader) request
                .decodeCommandCustomHeader(ConsumeMessageDirectlyResultRequestHeader.class);

        final MessageExt msg = MessageDecoder.decode(ByteBuffer.wrap(request.getBody()));

        ConsumeMessageDirectlyResult result =
            this.mqClientFactory.consumeMessageDirectly(msg, requestHeader.getConsumerGroup(), requestHeader.getBrokerName());

        if (null != result) {
            response.setCode(ResponseCode.SUCCESS);
            response.setBody(result.encode());
        } else {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark(String.format("The Consumer Group <%s> not exist in this consumer", requestHeader.getConsumerGroup()));
        }

        return response;
    }

MQClientInstance.consumeMessageDirectly

org.apache.rocketmq.client.impl.factory.MQClientInstance.consumeMessageDirectly(MessageExt, String, String)

    public ConsumeMessageDirectlyResult consumeMessageDirectly(final MessageExt msg,
        final String consumerGroup,
        final String brokerName) {
        MQConsumerInner mqConsumerInner = this.consumerTable.get(consumerGroup);
        if (null != mqConsumerInner) {
            DefaultMQPushConsumerImpl consumer = (DefaultMQPushConsumerImpl) mqConsumerInner;

            ConsumeMessageDirectlyResult result = consumer.getConsumeMessageService().consumeMessageDirectly(msg, brokerName);
            return result;
        }

        return null;
    }

ConsumeMessageConcurrentlyService.consumeMessageDirectly

org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.consumeMessageDirectly(MessageExt, String)

 /**
     * 直接消费消息
     */
    @Override
    public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) {
        ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult();
        result.setOrder(false);
        result.setAutoCommit(true);

        List<MessageExt> msgs = new ArrayList<MessageExt>();
        msgs.add(msg);
        MessageQueue mq = new MessageQueue();
        mq.setBrokerName(brokerName);
        mq.setTopic(msg.getTopic());
        mq.setQueueId(msg.getQueueId());

        ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(mq);

        this.resetRetryTopic(msgs);

        final long beginTime = System.currentTimeMillis();

        log.info("consumeMessageDirectly receive new message: {}", msg);

        try {//DefaultRocketMQListenerContainer 下的 接口实现,直接消费消息
            ConsumeConcurrentlyStatus status = this.messageListener.consumeMessage(msgs, context);
            if (status != null) {
                switch (status) {
                    case CONSUME_SUCCESS:
                        result.setConsumeResult(CMResult.CR_SUCCESS);
                        break;
                    case RECONSUME_LATER:
                        result.setConsumeResult(CMResult.CR_LATER);
                        break;
                    default:
                        break;
                }
            } else {
                result.setConsumeResult(CMResult.CR_RETURN_NULL);
            }
        } catch (Throwable e) {
            result.setConsumeResult(CMResult.CR_THROW_EXCEPTION);
            result.setRemark(RemotingHelper.exceptionSimpleDesc(e));

            log.warn(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s",
                RemotingHelper.exceptionSimpleDesc(e),
                ConsumeMessageConcurrentlyService.this.consumerGroup,
                msgs,
                mq), e);
        }

        result.setSpentTimeMills(System.currentTimeMillis() - beginTime);

        log.info("consumeMessageDirectly Result: {}", result);

        return result;
    }

参考文档

RocketMQ 消费消息过程分析

转载于:https://my.oschina.net/liangxiao/blog/3054547

<think>嗯,我现在需要帮助用户理解RocketMQ的顺序消费原理。根据用户提供的引用资料,我得先整理一下这些信息,然后按照系统级指令来组织回答。首先,用户要求用中文回答,行内数学表达式用$...$,独立公式用$$...$$,不过看起来这个问题可能不太需要数学公式,但如果有的话要注意格式。然后需要生成相关问题,回答要结构清晰,分步骤说明。 首先,我需要确认用户的问题:RocketMQ的顺序消息消费是如何实现的?用户希望得到深入解析,所以需要详细说明各个部分的工作原理。 根据引用[2],顺序消息的基本原理是确保消息按特定顺序被消费,比如同一订单的操作需要按顺序处理。RocketMQ采用队列级别的顺序性,即同一业务标识的消息会被分配到同一个队列中,消费者串行处理。这里可能需要解释队列和分区的关系,以及如何保证同一队列的消息顺序。 引用[3]提到,RocketMQ的Rebalance机制由每个Consumer自己处理,没有Leader,这可能影响消息分配。而引用[4]和[5]提到了ProcessQueue中的consumingMsgOrderlyTreeMap和锁机制,说明在消费时如何临时存储消息并保证顺序处理。 接下来,我需要将这些点组织成一个逻辑流程。比如,顺序消息的生产、存储、分配和消费。生产阶段需要指定业务标识,比如订单ID,通过哈希算法分配到同一队列。存储时队列本身是FIFO的。消费阶段,消费者通过Rebalance获取队列,然后每个队列由独立的线程处理,加锁确保串行处理,避免并发问题。 可能还需要提到分布式锁,如引用[5]中的tryLockLaterAndReconsume方法,用于在消费失败后重试,保持顺序。此外,ProcessQueue中的TreeMap结构用来维护消息的顺序,确保处理时按顺序取出。 用户可能存在的疑问包括:如何保证同一队列的顺序?消费者故障时如何处理?Rebalance如何影响顺序消费?需要逐一解答这些问题。 在回答结构上,应该分点解析,比如消息生产、队列分配、消费流程、锁机制、异常处理等。每个部分引用对应的资料来支持说明,比如引用[2]说明队列分配,引用[4]说明TreeMap的使用,引用[5]说明锁机制。 另外,需要生成相关问题,可能涉及顺序消息的应用场景、与Kafka的区别、性能优化等,根据引用资料中的内容来设计问题。 最后,确保回答中没有使用Markdown格式,行内公式正确,独立公式单独成段,但当前问题可能不需要数学公式,所以可能不需要处理这部分。但如果有涉及到算法或公式,比如哈希算法,可能需要用行内公式表示,比如$hash(key) \% queueNum$,不过根据示例,用户可能接受中文描述。 检查所有引用是否恰当,每个引用标识对应正确的资料,比如引用[2]对应顺序消息的基本原理,引用[4]对应consumingMsgOrderlyTreeMap,引用[5]对应锁机制等。确保回答真实可靠,结构清晰,帮助用户逐步理解。</think>### RocketMQ顺序消息消费实现原理分步解析 #### 一、核心设计原则 RocketMQ通过**队列级顺序性**实现顺序消息消费,遵循三个基本原则: 1. **相同业务标识的消息分配到同一队列** 生产者通过$hash(key) \% queueNum$算法将同一业务标识(如订单ID)的消息固定分配到特定队列[^2] 2. **队列存储保持FIFO特性** 每个队列内部严格保证消息的写入顺序与存储顺序一致 3. **消费者串行处理队列消息** 每个队列由独立的消费线程处理,禁止并发操作 #### 二、实现流程详解 1. **消息生产阶段** ```java Message message = new Message("OrderTopic", "OrderID_1001", "订单创建".getBytes()); SendResult sendResult = producer.send(message, new MessageQueueSelector() { @Override public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) { // 根据订单ID选择队列 int index = arg.hashCode() % mqs.size(); return mqs.get(index); } }, "OrderID_1001"); ``` 通过`MessageQueueSelector`保证相同订单ID的消息始终发送到同一队列[^2] 2. **消息存储结构 $$存储结构=Topic+Queue+MessageList$$ 每个队列维护物理偏移量(commitlog)和逻辑偏移量(consumequeue)的双层索引结构 3. **消费分配阶段** - 采用**主动拉取+长轮询**模式 - 消费者通过`RebalanceService`完成队列分配 - 每个消费者独立维护分配到的队列列表[^3] 4. **消息处理流程** ```plantuml @startuml group 消费线程 -> 获取队列锁 -> 从ProcessQueue拉取消息 -> 存入consumingMsgOrderlyTreeMap -> 执行业务处理 -> 提交消费位点 -> 释放队列锁 @enduml ``` - 使用`consumingMsgOrderlyTreeMap`临时存储待处理消息,维护消息顺序[^4] - 通过`ConsumeMessageOrderlyService`实现顺序消费服务[^5] #### 三、关键控制机制 1. **分布式锁控制** ```java public void run() { if (this.processQueue.isLocked()) { // 执行消息处理 } else { ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume( this.messageQueue, this.processQueue, 100); } } ``` 消费前必须获取队列锁,失败时触发重试机制[^5] 2. **异常处理策略** | 异常类型 | 处理方式 | |---|---| | 业务处理失败 | 自动重试当前消息(需业务保证幂等性) | | 网络中断 | 触发Rebalance重新分配队列 | | Broker宕机 | 切换至从节点继续消费 | 3. **顺序性保障边界** $$保障范围=同一队列+单线程处理+异常恢复机制$$ 在以下场景可能破坏顺序性: - 生产者跨线程发送同一业务标识的消息 - Broker主从切换时存在未复制消息 - 消费者并行处理多个队列 #### 四、性能优化实践 1. **批量加锁机制**:对连续消息进行批量锁申请 2. **本地缓存预取**:提前加载后续消息消费端内存 3. **零拷贝传输**:使用`FileChannel.map()`实现内存映射
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值