当消息生产者提交的消息存储在CommitLog文件中时,会及时更新ConsumeQueue、IndexFile。RocketMQ通过开启一个线程ReputMessageService来准实时准发CommitLog文件更新事件,相应的任务处理器根据转发的消息及时更新consumeQueue、IndexFile文件。
消息消费转发的核心实现在StoreMessage的doReput方法中实现:
1、返回 reputFromOffset 偏移量开始的全部有效数据(commitlog 文件)。然后循环 读取每一条消息。
2、从result 返回的 ByteBuffer 中循环读取消息,一次读取一条,创建 DispatchRequest对象( 在DispatchRequest中,如果消息长度大于 ,则调用 doDispatch 方法)。最终将分别调用 CommitLogDispatcherBuildConsumeQueue (构建消息消费队 )、 CommitLogDispatcherBuildlndex (构建索引文件)
根据消息更新ConsumeQueue:
1)、根据消息主题与队列 ID ,先获取对应的 ConumeQueue文件,其逻辑比较简单, 因为每一个消息主题对应一个消息消费队列目录, 然后主题下每一个消息队列对应一个文件夹,然后取出该文件夹最后的ConsumeQueue文件即可
public void putMessagePositionInfo(DispatchRequest dispatchRequest){
ConsumeQueue cq = this.findConsumeQueue
(dispatchRequest.getTopic(),dispatchRequest.getQueueId());
cq.putMessagePositionInfoWrapper(dispatchRequest);
}
2)、依次将消息偏移量、消息长度、tag hashcode写入到ByteBuffer中,并根据consumeQueueOffset计算ConsumeQueue中的物理地址,将内容追加到ConsumeQueue的内存映射内存中(本操作只追击不刷盘),ConsumeQueue的刷盘方式固定为异步刷盘模式。
this.byteBufferIndex.flip();
this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE);
this.byteBufferIndex.putLong(offset);
this.byteBufferIndex.putInt(size);
this.byteBufferIndex.putLong(tagsCode);
final long expectLogicOffset = cqOffset * CQ_STORE_UNIT_SIZE;
MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile
(expectLogicOffset);
if(mappedFile!=null){
Return mappedFile.appendMessage(this.byteBufferIndex.array());
}
根据消息更新Index索引文件:
Hash索引文件转发任务实现类是CommitDispatcherBuildIndex,首先判断isMessageIndexEnable设置为true,则调用IndexService的buildIndex方法构建Hash索引,否则忽略本次转发任务。
1、获取或创建 IndexFile 文件并获取所有文件最大的物理偏移量。如果该消息的物 理偏移量小于索引文件中的物理偏移,则说明是重复数据,忽略本次索引构建
IndexFile indexFile = retryGetAndCreateIndexFile();
if(indexFile != null){
Long endPhyOffset = indexFile.getEndPhyOffset();
DispatchRequestmsg = req;
Stringtopic = msg.getTopic();
Stringkeys = msg.getKeys();
if(msg.getCommitLogOffset() < endPhyOffset){
return;
}
}
2、如果消息的唯一键不为空,则添加到Hash索引中,以便加速根据唯一键检索消息
if(req.getUniqKey() != null){
indexFile = putKey(indexFile,msg,buildKey(topic,req.getUniqKey()));
if(indexFile == null){
log.error("putKeyerrorcommitlog{}uniqkey{}",req.getCommitLogOffset(),req.getUniqKey());
return;
}
}
3、构建索引键,RocketMQ支持为同一个消息建立多个索引,多个索引键空格分开
if(keys!=null&&keys.length()>0){
String[]keyset=keys.split(MessageConst.KEY_SEPARATOR);
for(inti=0;i<keyset.length;i++){
Stringkey=keyset[i];
if(key.length()>0){
indexFile=putKey(indexFile,msg,buildKey(topic,key));
if(indexFile==null){
log.error("putKeyerrorcommitlog{}uniqkey{}",
req.getCommitLogOffset(),req.getUniqKey());
return;
}
}
}
}