持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
简述
RocketMQ拉取消费主要通过生产消费消息特点可以做分析,下面列出特点与主要实现的代码ReputMessageService#doReput
的源码做Broker的拉取消费分析
特点
RocketMQ生产消费消息特点如下:
Producer
生产消息,指定topic
、queueId
。会将消息发送给Broker
。Consumer
从Broker
拉取消息。传输消息的结构是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
里面MappedFile
从reputFromOffset
开始的MappedByteBuffer
。遍历这个MappedByteBuffer
,生成重放消息重放调度请求 (DispatchRequest
) 。请求里主要包含一条消息 (Message) 或者文件尾(BLANK)的基本信息。
判断是有效的消息会发送通知notifyMessageArrive4MultiQueue(dispatchRequest)
DispatchRequest执行调度请求包括两件事
- 给非事务或事务提交的
Message
添加位置信息(putMessagePositionInfo
)到ConsumeQueue
;添加位置信息底层调用的是putMessagePositionInfoWrapper
方法,putMessagePositionInfoWrapper
会循环重试写入位置信息,写入成功后,会用存储的时间作为check point
(defaultMessageStore.getStoreCheckpoint().setLogicsMsgTimestamp(storeTimestamp);
) - 建立索引信息(
buildIndex
)到IndexFile
流程图
整个doReput
过程如下图:
小结
通过源码与流程图可以清晰看出Broker拉取消费的主要代码doReput
的执行流程,其中包括读取Message
,建立消息位置与索引信息、存储的底层的MappedFile
文件,统计,轮询读取等等这些步骤。