重新认识 RocketMQ (4)- Broker拉取消费

104 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

简述

RocketMQ拉取消费主要通过生产消费消息特点可以做分析,下面列出特点与主要实现的代码ReputMessageService#doReput的源码做Broker的拉取消费分析

特点

RocketMQ生产消费消息特点如下:

  • Producer生产消息,指定topicqueueId。会将消息发送给Broker
  • ConsumerBroker拉取消息。传输消息的结构是ConsumeQueue,有两种类型:MESSAGE_POSITION_INFO消息位置信息、BLANK : 文件前置空白占位。
  • 当历史Message被删除时,需要用BLANK占位被删除的消息。
  • ConsumeQueue位置信息存储在Commit Log中,存储主要方法是ReputMessageService#doReput(),线程不断执行生成消息位置信息到消费队列(ConsumeQueue)、生成消息索引到索引文件(IndexFile)

ReputMessageService#doReput源码

private void doReput() {
            // 设置最小位置
            if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) {
                log.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.",
                    this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset());
                this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset();
            }
            // 循环处理MappedBuffer
            for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) {

                if (DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable()
                    && this.reputFromOffset >= DefaultMessageStore.this.getConfirmOffset()) {
                    break;
                }

                // 从commitLog中reputFromOffset位置获取MappedBuffer
                SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset);
                if (result != null) {
                    try {
                        this.reputFromOffset = result.getStartOffset();
                        // 遍历MappedByteBuffer
                        for (int readSize = 0; readSize < result.getSize() && doNext; ) {
                            DispatchRequest dispatchRequest =
                                DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false);
                            int size = dispatchRequest.getBufferSize() == -1 ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize();

                            if (dispatchRequest.isSuccess()) {
                                if (size > 0) {
                                    DefaultMessageStore.this.doDispatch(dispatchRequest);
                                    // 通知有新消息
                                    if (BrokerRole.SLAVE != DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole()
                                            && DefaultMessageStore.this.brokerConfig.isLongPollingEnable()
                                            && DefaultMessageStore.this.messageArrivingListener != null) {
                                        DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
                                            dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
                                            dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),
                                            dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());
                                        notifyMessageArrive4MultiQueue(dispatchRequest);
                                    }

                                    this.reputFromOffset += size;
                                    readSize += size;
                                    // 记录统计信息
                                    if (DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) {
                                        DefaultMessageStore.this.storeStatsService
                                            .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).add(1);
                                        DefaultMessageStore.this.storeStatsService
                                            .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic())
                                            .add(dispatchRequest.getMsgSize());
                                    }
                                } else if (size == 0) {
                                    this.reputFromOffset = DefaultMessageStore.this.commitLog.rollNextFile(this.reputFromOffset);
                                    readSize = result.getSize();
                                }
                            } else if (!dispatchRequest.isSuccess()) {

                                if (size > 0) {
                                    log.error("[BUG]read total count not equals msg total size. reputFromOffset={}", reputFromOffset);
                                    this.reputFromOffset += size;
                                } else {
                                    doNext = false;
                                    // If user open the dledger pattern or the broker is master node,
                                    // it will not ignore the exception and fix the reputFromOffset variable
                                    if (DefaultMessageStore.this.getMessageStoreConfig().isEnableDLegerCommitLog() ||
                                        DefaultMessageStore.this.brokerConfig.getBrokerId() == MixAll.MASTER_ID) {
                                        log.error("[BUG]dispatch message to consume queue error, COMMITLOG OFFSET: {}",
                                            this.reputFromOffset);
                                        this.reputFromOffset += result.getSize() - readSize;
                                    }
                                }
                            }
                        }
                    } finally {
                        result.release();
                    }
                } else {
                    doNext = false;
                }
            }
        }

源码解析

首先获取CommitLog里面MappedFilereputFromOffset开始的MappedByteBuffer。遍历这个MappedByteBuffer,生成重放消息重放调度请求 (DispatchRequest) 。请求里主要包含一条消息 (Message) 或者文件尾(BLANK)的基本信息。 判断是有效的消息会发送通知notifyMessageArrive4MultiQueue(dispatchRequest)

DispatchRequest执行调度请求包括两件事

  • 给非事务或事务提交的Message添加位置信息(putMessagePositionInfo)到ConsumeQueue;添加位置信息底层调用的是putMessagePositionInfoWrapper方法,putMessagePositionInfoWrapper会循环重试写入位置信息,写入成功后,会用存储的时间作为check pointdefaultMessageStore.getStoreCheckpoint().setLogicsMsgTimestamp(storeTimestamp);
  • 建立索引信息(buildIndex)到IndexFile

流程图

整个doReput过程如下图:

reput.png

小结

通过源码与流程图可以清晰看出Broker拉取消费的主要代码doReput的执行流程,其中包括读取Message,建立消息位置与索引信息、存储的底层的MappedFile文件,统计,轮询读取等等这些步骤。