美文网首页rocketMq理论与实践
RocketMQ consumer 并行消费过程

RocketMQ consumer 并行消费过程

作者: 晴天哥_王志 | 来源:发表于2020-05-03 23:43 被阅读0次

    系列

    开篇

    • 这个系列的主要目的是介绍RocketMq consumer的原理和用法,在这个系列当中会介绍 consumer的启动流程、consumer Rebalance的过程、consumer注册过程、consumer 并行消费过程、consumer 有序消费过程。

    • 这篇文章介绍consumer 并行消费过程,分析consumer并行消费的实现逻辑。

    consumer消息获取触发机制

    RebalanceService

    public class RebalanceService extends ServiceThread {
        private static long waitInterval =
            Long.parseLong(System.getProperty(
                "rocketmq.client.rebalance.waitInterval", "20000"));
        private final InternalLogger log = ClientLogger.getLog();
        private final MQClientInstance mqClientFactory;
    
        public RebalanceService(MQClientInstance mqClientFactory) {
            this.mqClientFactory = mqClientFactory;
        }
    
        @Override
        public void run() {
            log.info(this.getServiceName() + " service started");
    
            while (!this.isStopped()) {
                this.waitForRunning(waitInterval);
                this.mqClientFactory.doRebalance();
            }
    
            log.info(this.getServiceName() + " service end");
        }
    
        @Override
        public String getServiceName() {
            return RebalanceService.class.getSimpleName();
        }
    }
    
    • RebalanceService线程启动任务后会执行MQClientInstance#doRebalance
    • RebalanceService服务充当consumer消费的触发器。

    MQClientInstance#doRebalance

    public class MQClientInstance {
    
        public void doRebalance() {
            for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
                MQConsumerInner impl = entry.getValue();
                if (impl != null) {
                    try {
                        impl.doRebalance();
                    } catch (Throwable e) {
                        log.error("doRebalance exception", e);
                    }
                }
            }
        }
    }
    
    public class DefaultMQPushConsumerImpl implements MQConsumerInner {
    
        @Override
        public void doRebalance() {
            if (!this.pause) {
                this.rebalanceImpl.doRebalance(this.isConsumeOrderly());
            }
        }
    }
    
    • 遍历consumerTable获取consumer group对应的DefaultMQPushConsumerImpl对象执行doRebalance。
    • consumerTable的数据格式如上图所示。

    RebalanceImpl#doRebalance

    public abstract class RebalanceImpl {
    
        public void doRebalance(final boolean isOrder) {
            // 遍历consumer对应的订阅信息SubscriptionData,key为topic,value为SubscriptionData
            Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
            if (subTable != null) {
                for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
                    final String topic = entry.getKey();
                    try {
                        this.rebalanceByTopic(topic, isOrder);
                    } catch (Throwable e) {
                        if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                            log.warn("rebalanceByTopic Exception", e);
                        }
                    }
                }
            }
    
            this.truncateMessageQueueNotMyTopic();
        }
    
    • 遍历consumer的topic订阅信息,依次进行rebalanceByTopic操作。
    • subTable的数据格式如上图所示。

    RebalanceImpl#rebalanceByTopic

    public abstract class RebalanceImpl {
    
        private void rebalanceByTopic(final String topic, final boolean isOrder) {
            switch (messageModel) {
                case BROADCASTING: {
                    // 省略代码
                    break;
                }
                case CLUSTERING: {
                    Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
                    List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
                    if (null == mqSet) {
                        if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                            log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
                        }
                    }
    
                    if (null == cidAll) {
                        log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
                    }
    
                    if (mqSet != null && cidAll != null) {
                        List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
                        mqAll.addAll(mqSet);
    
                        Collections.sort(mqAll);
                        Collections.sort(cidAll);
    
                        AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
    
                        List<MessageQueue> allocateResult = null;
                        try {
                            allocateResult = strategy.allocate(
                                this.consumerGroup,
                                this.mQClientFactory.getClientId(),
                                mqAll,
                                cidAll);
                        } catch (Throwable e) {
                            log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),
                                e);
                            return;
                        }
    
                        Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
                        if (allocateResult != null) {
                            allocateResultSet.addAll(allocateResult);
                        }
                        // 核心updateProcessQueueTableInRebalance操作
                        boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
                        if (changed) {
                            log.info(
                                "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
                                strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
                                allocateResultSet.size(), allocateResultSet);
                            this.messageQueueChanged(topic, mqSet, allocateResultSet);
                        }
                    }
                    break;
                }
                default:
                    break;
            }
        }
    }
    
    • RebalanceImpl#rebalanceByTopic内部执行updateProcessQueueTableInRebalance来对topic下指定的MessageQueue进行消息消费。

    RebalanceImpl#updateProcessQueueTableInRebalance

    public abstract class RebalanceImpl {
    
        private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
            final boolean isOrder) {
    
            boolean changed = false;
    
            // 省略相关代码
    
            List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
            for (MessageQueue mq : mqSet) {
                if (!this.processQueueTable.containsKey(mq)) {
                    if (isOrder && !this.lock(mq)) {
                        log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
                        continue;
                    }
                    // 移除错误的offset数据
                    this.removeDirtyOffset(mq);
    
                    // 新增ProcessQueue和PullRequest对象
                    ProcessQueue pq = new ProcessQueue();
                    long nextOffset = this.computePullFromWhere(mq);
                    if (nextOffset >= 0) {
                        ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
                        if (pre != null) {
                            log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
                        } else {
                            log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
                            PullRequest pullRequest = new PullRequest();
                            pullRequest.setConsumerGroup(consumerGroup);
                            pullRequest.setNextOffset(nextOffset);
                            pullRequest.setMessageQueue(mq);
                            pullRequest.setProcessQueue(pq);
                            pullRequestList.add(pullRequest);
                            changed = true;
                        }
                    } else {
                        log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
                    }
                }
            }
            // 分发pullRequestList对象
            this.dispatchPullRequest(pullRequestList);
    
            return changed;
        }
    }
    
    • updateProcessQueueTableInRebalance会创建pullRequest形成pullRequestList。
    • 通过dispatchPullRequest()来提交PullRequest任务。

    RebalancePushImpl#dispatchPullRequest

    public class RebalancePushImpl extends RebalanceImpl {
        
        private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;
    
        @Override
        public void dispatchPullRequest(List<PullRequest> pullRequestList) {
            for (PullRequest pullRequest : pullRequestList) {
                this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
                log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest);
            }
        }
    }
    
    public class DefaultMQPushConsumerImpl implements MQConsumerInner {
    
        public void executePullRequestImmediately(final PullRequest pullRequest) {
            this.mQClientFactory.getPullMessageService().executePullRequestImmediately(pullRequest);
        }
    }
    
    • RebalancePushImpl#dispatchPullRequest负责向PullMessageService的pullRequestQueue投递任务。

    消息拉取消费流程

    • 封装消息拉取请求为PullRequest提交PullMessageService#pullRequestQueue。
    • PullMessageService单线程处理消息拉取任务PullRequest。
    • PullMessageService的消息拉取结果封装成ConsumeRequest。
    • ConsumeMessageConcurrentlyService#consumeExecutor负责执行ConsumeRequest对象。
    public class PullRequest {
        private String consumerGroup;
        private MessageQueue messageQueue;
        private ProcessQueue processQueue;
        private long nextOffset;
        private boolean lockedFirst = false;
    }
    
    • messageQueue表示该PullRequest负责拉取的queue。
    • processQueue表示拉取的消息保存的队列。
    class ConsumeRequest implements Runnable {
        private final List<MessageExt> msgs;
        private final ProcessQueue processQueue;
        private final MessageQueue messageQueue;
    }                   
    
    • msgs表示拉取的消息。
    • messageQueue表示该ConsumeRequest负责的queue。
    • processQueue表示拉取的消息保存的队列。
    public class ProcessQueue {
        public final static long REBALANCE_LOCK_MAX_LIVE_TIME =
            Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockMaxLiveTime", "30000"));
        public final static long REBALANCE_LOCK_INTERVAL = Long.parseLong(System.getProperty("rocketmq.client.rebalance.lockInterval", "20000"));
        private final static long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000"));
        private final InternalLogger log = ClientLogger.getLog();
        private final ReadWriteLock lockTreeMap = new ReentrantReadWriteLock();
        private final TreeMap<Long, MessageExt> msgTreeMap = new TreeMap<Long, MessageExt>();
        private final AtomicLong msgCount = new AtomicLong();
        private final AtomicLong msgSize = new AtomicLong();
        private final Lock lockConsume = new ReentrantLock();
    }
    
    • msgTreeMap用来保存拉取的消息体MessageExt,是一个TreeMap类型对象。

    PullMessageService

    public class PullMessageService extends ServiceThread {
    
        private final LinkedBlockingQueue<PullRequest> pullRequestQueue = new LinkedBlockingQueue<PullRequest>();
        private final MQClientInstance mQClientFactory;
    
        public void executePullRequestImmediately(final PullRequest pullRequest) {
            try {
                this.pullRequestQueue.put(pullRequest);
            } catch (InterruptedException e) {
                log.error("executePullRequestImmediately pullRequestQueue.put", e);
            }
        }
    
        @Override
        public void run() {
            log.info(this.getServiceName() + " service started");
    
            while (!this.isStopped()) {
                try {
                    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");
        }
    
    
        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);
            }
        }
    }
    
    • PullMessageService是个单线程任务,实现生产者消费者模型
    • executePullRequestImmediately负责往队列添加PullRequest,run()负责获取PullRequest对象负责拉取消息。
    • pullMessage负责执行单个PullRequest的数据拉取。

    DefaultMQPushConsumerImpl

    public class DefaultMQPushConsumerImpl implements MQConsumerInner {
    
        public void pullMessage(final PullRequest pullRequest) {
            final ProcessQueue processQueue = pullRequest.getProcessQueue();
            if (processQueue.isDropped()) {
                log.info("the pull request[{}] is dropped.", pullRequest.toString());
                return;
            }
    
            pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());
    
            try {
                this.makeSureStateOK();
            } catch (MQClientException e) {
                log.warn("pullMessage exception, consumer state not ok", e);
                this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
                return;
            }
            
            // 省略相关状态判断的逻辑
    
            // 指定消息拉取的回调函数PullCallback
            PullCallback pullCallback = new PullCallback() {};
    
            // 执行消息的拉取
            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, pullTimeDelayMillsWhenException);
            }
        }
    }
    
    • DefaultMQPushConsumerImpl#pullMessage内部构建消息拉取成功的回调函数。
    • DefaultMQPushConsumerImpl#pullMessage执行pullKernelImpl实现消息的拉取。

    PullAPIWrapper

    public class PullAPIWrapper {
    
        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;
            }
        }
    }
    
    • pullKernelImpl查找brokerAddr,封装PullMessageRequestHeader对象。
    • pullKernelImpl内部调用MQClientAPIImpl#pullMessage。

    MQClientAPIImpl

    public class MQClientAPIImpl {
    
        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;
        }
    
    
        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);
                        } 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()));
                        }
                    }
                }
            });
        }
    
        private PullResult processPullResponse(
            final RemotingCommand response) throws MQBrokerException, RemotingCommandException {
            PullStatus pullStatus = PullStatus.NO_NEW_MSG;
            switch (response.getCode()) {
                case ResponseCode.SUCCESS:
                    pullStatus = PullStatus.FOUND;
                    break;
                case ResponseCode.PULL_NOT_FOUND:
                    pullStatus = PullStatus.NO_NEW_MSG;
                    break;
                case ResponseCode.PULL_RETRY_IMMEDIATELY:
                    pullStatus = PullStatus.NO_MATCHED_MSG;
                    break;
                case ResponseCode.PULL_OFFSET_MOVED:
                    pullStatus = PullStatus.OFFSET_ILLEGAL;
                    break;
    
                default:
                    throw new MQBrokerException(response.getCode(), response.getRemark());
            }
    
            PullMessageResponseHeader responseHeader =
                (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);
    
            return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(),
                responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody());
        }
    }
    
    • pullMessageAsync发起异步查询并通过processPullResponse解析结果。
    • pullCallback.onSuccess处理查询结果,pullCallback是回调函数。

    DefaultMQPushConsumerImpl

    public class DefaultMQPushConsumerImpl implements MQConsumerInner {
    
        public void pullMessage(final PullRequest pullRequest) {
    
            // 指定消息拉取的回调函数PullCallback
            PullCallback pullCallback = new PullCallback() {
                @Override
                public void onSuccess(PullResult pullResult) {
                    if (pullResult != null) {
                        pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
                            subscriptionData);
    
                        switch (pullResult.getPullStatus()) {
                            case FOUND:
                                long prevRequestOffset = pullRequest.getNextOffset();
                                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());
                                    // 提交消息结果给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:
                                pullRequest.setNextOffset(pullResult.getNextBeginOffset());
    
                                DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);
    
                                DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                                break;
                            case NO_MATCHED_MSG:
                                pullRequest.setNextOffset(pullResult.getNextBeginOffset());
    
                                DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);
    
                                DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                                break;
                            case OFFSET_ILLEGAL:
                                log.warn("the pull request offset illegal, {} {}",
                                    pullRequest.toString(), pullResult.toString());
                                pullRequest.setNextOffset(pullResult.getNextBeginOffset());
    
                                pullRequest.getProcessQueue().setDropped(true);
                                DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() {
    
                                    @Override
                                    public void run() {
                                        try {
                                            DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(),
                                                pullRequest.getNextOffset(), false);
    
                                            DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue());
    
                                            DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue());
    
                                            log.warn("fix the pull request offset, {}", pullRequest);
                                        } catch (Throwable e) {
                                            log.error("executeTaskLater Exception", e);
                                        }
                                    }
                                }, 10000);
                                break;
                            default:
                                break;
                        }
                    }
                }
    
                @Override
                public void onException(Throwable e) {
                    if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                        log.warn("execute the pull request exception", e);
                    }
    
                    DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
                }
            };
        }
    }
    
    • PullCallback根据查询结果的状态进行不同的分支处理,关注状态为FOUND的场景。
    • 首先将消息查询结果放置在processQueue当中,该processQueue取自PullRequest,processQueue.putMessage(pullResult.getMsgFoundList())。
    • 其次将消息查询结果封装成ConsumeRequest提交consumeExecutor当中。
    • 最后再次提交拉取任务pullRequest。

    ConsumeMessageConcurrentlyService

    public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
    
        private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;
        private final DefaultMQPushConsumer defaultMQPushConsumer;
        private final MessageListenerConcurrently messageListener;
        private final BlockingQueue<Runnable> consumeRequestQueue;
        private final ThreadPoolExecutor consumeExecutor;
        private final String consumerGroup;
        private final ScheduledExecutorService scheduledExecutorService;
        private final ScheduledExecutorService cleanExpireMsgExecutors;
    
        @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 {
                    // 由consumeExecutor.submit提交执行
                    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 consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue);
                    try {
                        // 由consumeExecutor.submit提交执行
                        this.consumeExecutor.submit(consumeRequest);
                    } catch (RejectedExecutionException e) {
                        for (; total < msgs.size(); total++) {
                            msgThis.add(msgs.get(total));
                        }
    
                        this.submitConsumeRequestLater(consumeRequest);
                    }
                }
            }
        }
    }
    
    • submitConsumeRequest负责封装consumeRequest并提交consumeExecutor。

    ConsumeRequest

    public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
    
        class ConsumeRequest implements Runnable {
            private final List<MessageExt> msgs;
            private final ProcessQueue processQueue;
            private final MessageQueue messageQueue;
    
            public ConsumeRequest(List<MessageExt> msgs, ProcessQueue processQueue, MessageQueue messageQueue) {
                this.msgs = msgs;
                this.processQueue = processQueue;
                this.messageQueue = messageQueue;
            }
    
            public List<MessageExt> getMsgs() {
                return msgs;
            }
    
            public ProcessQueue getProcessQueue() {
                return processQueue;
            }
    
            @Override
            public void run() {
                // 1、获取消费处理函数messageListener
                MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener;
                // 2、创建消费上下文context
                ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
                ConsumeConcurrentlyStatus status = null;
                // 3、设置msgs的topic为%RETRY%consumer_group
                defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup());
    
                ConsumeMessageContext consumeMessageContext = null;
                long beginTimestamp = System.currentTimeMillis();
                boolean hasException = false;
                ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
                try {
                    if (msgs != null && !msgs.isEmpty()) {
                        for (MessageExt msg : msgs) {
                            MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis()));
                        }
                    }
    
                    // 4、调用listener.consumeMessage消费消息
                    status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
                } catch (Throwable e) {
                    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 (null == status) {
                    status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }
    
                ConsumeMessageConcurrentlyService.this.getConsumerStatsManager()
                    .incConsumeRT(ConsumeMessageConcurrentlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT);
    
                if (!processQueue.isDropped()) {
                    // 5、处理consumer消费的结果
                    ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this);
                } else {
                    log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs);
                }
            }
    
            public MessageQueue getMessageQueue() {
                return messageQueue;
            }
        }
    }
    
    • 1、获取消费处理函数messageListener。
    • 2、创建消费上下文context,每次都会重新new一个context。
    • 3、设置msgs的topic为%RETRY%consumer_group。
    • 4、调用listener.consumeMessage消费消息。
    • 5、处理consumer消费的结果。

    processConsumeResult

    public class ConsumeMessageConcurrentlyService implements ConsumeMessageService {
    
        public void processConsumeResult(
            final ConsumeConcurrentlyStatus status,
            final ConsumeConcurrentlyContext context,
            final ConsumeRequest consumeRequest
        ) {
            int ackIndex = context.getAckIndex();
    
            if (consumeRequest.getMsgs().isEmpty())
                return;
            // 消费成功的场景下ackIndex等于消息长度-1
            // 消费失败的场景下ackIndex=-1
            switch (status) {
                case CONSUME_SUCCESS:
                    if (ackIndex >= consumeRequest.getMsgs().size()) {
                        ackIndex = consumeRequest.getMsgs().size() - 1;
                    }
                    int ok = ackIndex + 1;
                    int failed = consumeRequest.getMsgs().size() - ok;
                    this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), ok);
                    this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), failed);
                    break;
                case RECONSUME_LATER:
                    ackIndex = -1;
                    this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(),
                        consumeRequest.getMsgs().size());
                    break;
                default:
                    break;
            }
    
            switch (this.defaultMQPushConsumer.getMessageModel()) {
                case BROADCASTING:
                    for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
                        MessageExt msg = consumeRequest.getMsgs().get(i);
                        log.warn("BROADCASTING, the message consume failed, drop it, {}", msg.toString());
                    }
                    break;
                case CLUSTERING:
                    // 针对发送失败情况ackIndex=-1,相当于这批次消息全部执行sendMessageBack
                    // sendMessageBack就是把消息发送到重试队列当中
                    List<MessageExt> msgBackFailed = new ArrayList<MessageExt>(consumeRequest.getMsgs().size());
                    for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
                        MessageExt msg = consumeRequest.getMsgs().get(i);
                        boolean result = this.sendMessageBack(msg, context);
                        if (!result) {
                            msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
                            msgBackFailed.add(msg);
                        }
                    }
                    // 针对存在消息消费失败的场景
                    if (!msgBackFailed.isEmpty()) {
                        consumeRequest.getMsgs().removeAll(msgBackFailed);
    
                        this.submitConsumeRequestLater(msgBackFailed, consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue());
                    }
                    break;
                default:
                    break;
            }
    
            // 执行消费位移持久化动作
            long offset = consumeRequest.getProcessQueue().removeMessage(consumeRequest.getMsgs());
            if (offset >= 0 && !consumeRequest.getProcessQueue().isDropped()) {
                this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), offset, true);
            }
        }
    
    
        public boolean sendMessageBack(final MessageExt msg, final ConsumeConcurrentlyContext context) {
            int delayLevel = context.getDelayLevelWhenNextConsume();
    
            // Wrap topic with namespace before sending back message.
            msg.setTopic(this.defaultMQPushConsumer.withNamespace(msg.getTopic()));
            try {
                this.defaultMQPushConsumerImpl.sendMessageBack(msg, delayLevel, context.getMessageQueue().getBrokerName());
                return true;
            } catch (Exception e) {
                log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e);
            }
    
            return false;
        }
    }
    
    • 消费成功的场景下ackIndex等于消息长度-1,消费失败的场景下ackIndex=-1。
    • 针对发送失败情况ackIndex=-1,相当于这批次消息全部执sendMessageBack,就是把消息发送到重试队列当中。
    • 目前看针对发送失败的情况除了投递重试队列,还会重新生成consumeRequest再次投递

    消费位移

    public class ProcessQueue {
    
        private final ReadWriteLock lockTreeMap = new ReentrantReadWriteLock();
        private final TreeMap<Long, MessageExt> msgTreeMap = new TreeMap<Long, MessageExt>();
    
    
        public boolean putMessage(final List<MessageExt> msgs) {
            boolean dispatchToConsume = false;
            try {
                this.lockTreeMap.writeLock().lockInterruptibly();
                try {
                    int validMsgCnt = 0;
                    for (MessageExt msg : msgs) {
                        MessageExt old = msgTreeMap.put(msg.getQueueOffset(), msg);
                        if (null == old) {
                            validMsgCnt++;
                            this.queueOffsetMax = msg.getQueueOffset();
                            msgSize.addAndGet(msg.getBody().length);
                        }
                    }
                    msgCount.addAndGet(validMsgCnt);
    
                    if (!msgTreeMap.isEmpty() && !this.consuming) {
                        dispatchToConsume = true;
                        this.consuming = true;
                    }
    
                    if (!msgs.isEmpty()) {
                        MessageExt messageExt = msgs.get(msgs.size() - 1);
                        String property = messageExt.getProperty(MessageConst.PROPERTY_MAX_OFFSET);
                        if (property != null) {
                            long accTotal = Long.parseLong(property) - messageExt.getQueueOffset();
                            if (accTotal > 0) {
                                this.msgAccCnt = accTotal;
                            }
                        }
                    }
                } finally {
                    this.lockTreeMap.writeLock().unlock();
                }
            } catch (InterruptedException e) {
                log.error("putMessage exception", e);
            }
    
            return dispatchToConsume;
        }
    
        public long removeMessage(final List<MessageExt> msgs) {
            long result = -1;
            final long now = System.currentTimeMillis();
            try {
                this.lockTreeMap.writeLock().lockInterruptibly();
                this.lastConsumeTimestamp = now;
                try {
                    if (!msgTreeMap.isEmpty()) {
                        result = this.queueOffsetMax + 1;
                        int removedCnt = 0;
                        for (MessageExt msg : msgs) {
                            MessageExt prev = msgTreeMap.remove(msg.getQueueOffset());
                            if (prev != null) {
                                removedCnt--;
                                msgSize.addAndGet(0 - msg.getBody().length);
                            }
                        }
                        msgCount.addAndGet(removedCnt);
    
                        if (!msgTreeMap.isEmpty()) {
                            result = msgTreeMap.firstKey();
                        }
                    }
                } finally {
                    this.lockTreeMap.writeLock().unlock();
                }
            } catch (Throwable t) {
                log.error("removeMessage exception", t);
            }
    
            return result;
        }
    }
    
    • ProcessQueue核心数据为msgTreeMap,其中key为消息的queueOffset,value为消息体。
    • msgTreeMap的数据结构为TreeMap,是个有序map。
    • removeMessage每次会从msgTreeMap移除已经成功消费的消息,然后获取msgTreeMap中的最小queueOffset,也就是成功处理的最小queueOffset。
    • 通过针对msgTreeMap对象的维护,保证了消息消费的位移的持久化。

    相关文章

      网友评论

        本文标题:RocketMQ consumer 并行消费过程

        本文链接:https://www.haomeiwen.com/subject/mcbrghtx.html