IndexFile
大小固定,文件名是创建时候的时间戳
IndexHeader(40字节) Slot Table(500w * 4字节) Index Linked List(4 * 500W * 20字节)
IndexHeader结构
beginTimestamp(8) 第一个索引消息保存broker的时间戳
endTimestamp(8) 最后一个索引消息保存broker的时间戳
beginPhyOffset(8) 第一个索引消息在commitLog的偏移地址
endPhyOffset(8) 最后一个索引消息在commitlog的偏移地址
hashSlotCount(4) 从0开始,计数,记录Slot Table使用个数
indexCount(4) 从1开始,计数,记录Index Linked List使用个数
Slot Table里面保存的是 Index Linked List的索引,最后一个index,例如从空开始连续存3个keyhash一样的
Slot Table中某个(keyhash % 500w 位置处)slot保存3,代表从链表的第三个开始找
Index Linked List 第3个的slotValue保存2,第2个的slotValue保存1,第1个的slotValue保存0,就这样形成了一个链表
Index Linked List每个节点20字节,结构为
keyHash(4) topic+key的hash值
phyOffset(8) 消息在commitLog中的偏移地址
timeDiff(4) 消息的保存时间戳减去IndexHeader的beginTimestamp
slotValue 上一个相同keyHash的节点在Index Linked List的位置
1.写索引过程
org.apache.rocketmq.store.DefaultMessageStore.CommitLogDispatcherBuildIndex.dispatch(DispatchRequest)
org.apache.rocketmq.store.index.IndexService.buildIndex(DispatchRequest)
拿到最后一个索引文件,可能新建新的索引文件,新建索引文件时候,起一个新线程flush上一个索引文件
先写UniqKey,然后keys
具体文件写索引过程
org.apache.rocketmq.broker.processor.QueryMessageProcessor.processRequest(ChannelHandlerContext, RemotingCommand)
broker处理查询消息
org.apache.rocketmq.store.DefaultMessageStore.queryMessage(String, String, int, long, long)
先调用indexService查询出所有符合条件的消息在commitLog的Offset
再使用commitLog根据offset拿到具体消息
org.apache.rocketmq.store.index.IndexService.queryOffset(String, String, int, long, long)
便利所有的索引文件,查询
org.apache.rocketmq.store.index.IndexFile.selectPhyOffset(List<Long>, String, int, long, long, boolean)
根据keyhash找到table位置,读出来就是最后一个索引记录,然后依次往前读,校验时间和keyhash,就拿到这个文件中的所有
符合条件的消息offset
大小固定,文件名是创建时候的时间戳
IndexHeader(40字节) Slot Table(500w * 4字节) Index Linked List(4 * 500W * 20字节)
IndexHeader结构
beginTimestamp(8) 第一个索引消息保存broker的时间戳
endTimestamp(8) 最后一个索引消息保存broker的时间戳
beginPhyOffset(8) 第一个索引消息在commitLog的偏移地址
endPhyOffset(8) 最后一个索引消息在commitlog的偏移地址
hashSlotCount(4) 从0开始,计数,记录Slot Table使用个数
indexCount(4) 从1开始,计数,记录Index Linked List使用个数
Slot Table里面保存的是 Index Linked List的索引,最后一个index,例如从空开始连续存3个keyhash一样的
Slot Table中某个(keyhash % 500w 位置处)slot保存3,代表从链表的第三个开始找
Index Linked List 第3个的slotValue保存2,第2个的slotValue保存1,第1个的slotValue保存0,就这样形成了一个链表
Index Linked List每个节点20字节,结构为
keyHash(4) topic+key的hash值
phyOffset(8) 消息在commitLog中的偏移地址
timeDiff(4) 消息的保存时间戳减去IndexHeader的beginTimestamp
slotValue 上一个相同keyHash的节点在Index Linked List的位置
1.写索引过程
org.apache.rocketmq.store.DefaultMessageStore.CommitLogDispatcherBuildIndex.dispatch(DispatchRequest)
org.apache.rocketmq.store.index.IndexService.buildIndex(DispatchRequest)
拿到最后一个索引文件,可能新建新的索引文件,新建索引文件时候,起一个新线程flush上一个索引文件
先写UniqKey,然后keys
具体文件写索引过程
org.apache.rocketmq.store.index.IndexFile.putKey(String, long, long)
public boolean putKey(final String key, final long phyOffset, final long storeTimestamp) {
if (this.indexHeader.getIndexCount() < this.indexNum) {//未写满
int keyHash = indexKeyHashMethod(key);
int slotPos = keyHash % this.hashSlotNum;//找到table位置
int absSlotPos = IndexHeader.INDEX_HEADER_SIZE + slotPos * hashSlotSize;//table位置的偏移地址
FileLock fileLock = null;
try {
// fileLock = this.fileChannel.lock(absSlotPos, hashSlotSize,
// false);
int slotValue = this.mappedByteBuffer.getInt(absSlotPos);//取得table位置处的值,也就是后面indexArray的索引
if (slotValue <= invalidIndex || slotValue > this.indexHeader.getIndexCount()) {
slotValue = invalidIndex;//越界情况为0
}
long timeDiff = storeTimestamp - this.indexHeader.getBeginTimestamp();//时间戳差
timeDiff = timeDiff / 1000;
if (this.indexHeader.getBeginTimestamp() <= 0) {
timeDiff = 0;
} else if (timeDiff > Integer.MAX_VALUE) {
timeDiff = Integer.MAX_VALUE;
} else if (timeDiff < 0) {
timeDiff = 0;
}
int absIndexPos =
IndexHeader.INDEX_HEADER_SIZE + this.hashSlotNum * hashSlotSize
+ this.indexHeader.getIndexCount() * indexSize;//计算后面indexArray的偏移地址
this.mappedByteBuffer.putInt(absIndexPos, keyHash);
this.mappedByteBuffer.putLong(absIndexPos + 4, phyOffset);
this.mappedByteBuffer.putInt(absIndexPos + 4 + 8, (int) timeDiff);
this.mappedByteBuffer.putInt(absIndexPos + 4 + 8 + 4, slotValue);//写入四个信息
this.mappedByteBuffer.putInt(absSlotPos, this.indexHeader.getIndexCount());//把最后一个array的索引写入table
if (this.indexHeader.getIndexCount() <= 1) {
this.indexHeader.setBeginPhyOffset(phyOffset);
this.indexHeader.setBeginTimestamp(storeTimestamp);
}
this.indexHeader.incHashSlotCount();//写header的一些信息
this.indexHeader.incIndexCount();
this.indexHeader.setEndPhyOffset(phyOffset);
this.indexHeader.setEndTimestamp(storeTimestamp);
return true;
} catch (Exception e) {
log.error("putKey exception, Key: " + key + " KeyHashCode: " + key.hashCode(), e);
} finally {
if (fileLock != null) {
try {
fileLock.release();
} catch (IOException e) {
log.error("Failed to release the lock", e);
}
}
}
} else {
log.warn("Over index file capacity: index count = " + this.indexHeader.getIndexCount()
+ "; index max num = " + this.indexNum);
}
return false;
}
2.根据索引查询消息过程
org.apache.rocketmq.broker.processor.QueryMessageProcessor.processRequest(ChannelHandlerContext, RemotingCommand)
broker处理查询消息
org.apache.rocketmq.store.DefaultMessageStore.queryMessage(String, String, int, long, long)
先调用indexService查询出所有符合条件的消息在commitLog的Offset
再使用commitLog根据offset拿到具体消息
org.apache.rocketmq.store.index.IndexService.queryOffset(String, String, int, long, long)
便利所有的索引文件,查询
org.apache.rocketmq.store.index.IndexFile.selectPhyOffset(List<Long>, String, int, long, long, boolean)
根据keyhash找到table位置,读出来就是最后一个索引记录,然后依次往前读,校验时间和keyhash,就拿到这个文件中的所有
符合条件的消息offset