持续创作,加速成长!这是我参与「掘金日新计划 · 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文件,统计,轮询读取等等这些步骤。