说在前面
继续解析管理请求之SEARCH_OFFSET_BY_TIMESTAMP 按时间查询offset,更多源码解析请关注“天河聊架构”微信公众号
源码解析
进入这个方法org.apache.rocketmq.broker.processor.AdminBrokerProcessor#searchOffsetByTimestamp 按时间查询offset
private RemotingCommand searchOffsetByTimestamp(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class);
final SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader();
final SearchOffsetRequestHeader requestHeader =
(SearchOffsetRequestHeader) request.decodeCommandCustomHeader(SearchOffsetRequestHeader.class);
// 按时间查询offset=》
long offset = this.brokerController.getMessageStore().getOffsetInQueueByTime(requestHeader.getTopic(), requestHeader.getQueueId(),
requestHeader.getTimestamp());
responseHeader.setOffset(offset);
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
}
进入到这个方法org.apache.rocketmq.store.DefaultMessageStore#getOffsetInQueueByTime 按时间查询offset
public long getOffsetInQueueByTime(String topic, int queueId, long timestamp) {
// 根据topic找到队列群 =》
ConsumeQueue logic = this.findConsumeQueue(topic, queueId);
if (logic != null) {
// 按时间查询offset=》
return logic.getOffsetInQueueByTime(timestamp);
}
return 0;
}
进入这个方法org.apache.rocketmq.store.DefaultMessageStore#findConsumeQueue 查询消费队列
public ConsumeQueue findConsumeQueue(String topic, int queueId) {
ConcurrentMap<Integer, ConsumeQueue> map = consumeQueueTable.get(topic);
if (null == map) {
ConcurrentMap<Integer, ConsumeQueue> newMap = new ConcurrentHashMap<Integer, ConsumeQueue>(128);
ConcurrentMap<Integer, ConsumeQueue> oldMap = consumeQueueTable.putIfAbsent(topic, newMap);
if (oldMap != null) {
map = oldMap;
} else {
map = newMap;
}
}
// 按queue id查找消费者队列
ConsumeQueue logic = map.get(queueId);
if (null == logic) {
ConsumeQueue newLogic = new ConsumeQueue(
topic,
queueId,
// 消费者队列存储地址 user.home/store/consumequeue
StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()),
// 每个文件存储默认30W
this.getMessageStoreConfig().getMapedFileSizeConsumeQueue(),
this);
ConsumeQueue oldLogic = map.putIfAbsent(queueId, newLogic);
if (oldLogic != null) {
logic = oldLogic;
} else {
logic = newLogic;
}
}
return logic;
}
往上返回到这个方法org.apache.rocketmq.store.ConsumeQueue#getOffsetInQueueByTime 按时间查询offset
public long getOffsetInQueueByTime(final long timestamp) {
// 根据时间找到映射的文件,文件可以知道最后一次修改的时间 =》
MappedFile mappedFile = this.mappedFileQueue.getMappedFileByTime(timestamp);
if (mappedFile != null) {
long offset = 0;
int low = minLogicOffset > mappedFile.getFileFromOffset() ? (int) (minLogicOffset - mappedFile.getFileFromOffset()) : 0;
int high = 0;
int midOffset = -1, targetOffset = -1, leftOffset = -1, rightOffset = -1;
long leftIndexValue = -1L, rightIndexValue = -1L;
// 获取最小的物理偏移量 =》
long minPhysicOffset = this.defaultMessageStore.getMinPhyOffset();
// =》
SelectMappedBufferResult sbr = mappedFile.selectMappedBuffer(0);
if (null != sbr) {
ByteBuffer byteBuffer = sbr.getByteBuffer();
high = byteBuffer.limit() - CQ_STORE_UNIT_SIZE;
try {
while (high >= low) {
midOffset = (low + high) / (2 * CQ_STORE_UNIT_SIZE) * CQ_STORE_UNIT_SIZE;
byteBuffer.position(midOffset);
long phyOffset = byteBuffer.getLong();
int size = byteBuffer.getInt();
if (phyOffset < minPhysicOffset) {
low = midOffset + CQ_STORE_UNIT_SIZE;
leftOffset = midOffset;
continue;
}
// 按物理offset从commitLog中获取存储时间=》
long storeTime =
this.defaultMessageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size);
if (storeTime < 0) {
return 0;
} else if (storeTime == timestamp) {
targetOffset = midOffset;
break;
} else if (storeTime > timestamp) {
high = midOffset - CQ_STORE_UNIT_SIZE;
rightOffset = midOffset;
rightIndexValue = storeTime;
} else {
low = midOffset + CQ_STORE_UNIT_SIZE;
leftOffset = midOffset;
leftIndexValue = storeTime;
}
}
if (targetOffset != -1) {
offset = targetOffset;
} else {
if (leftIndexValue == -1) {
offset = rightOffset;
} else if (rightIndexValue == -1) {
offset = leftOffset;
} else {
offset =
Math.abs(timestamp - leftIndexValue) > Math.abs(timestamp
- rightIndexValue) ? rightOffset : leftOffset;
}
}
return (mappedFile.getFileFromOffset() + offset) / CQ_STORE_UNIT_SIZE;
} finally {
// 映射文件释放=》
sbr.release();
}
}
}
return 0;
}
进入这个方法org.apache.rocketmq.store.MappedFileQueue#getMappedFileByTime 按时间查询映射文件
public MappedFile getMappedFileByTime(final long timestamp) {
Object[] mfs = this.copyMappedFiles(0);
if (null == mfs)
return null;
for (int i = 0; i < mfs.length; i++) {
MappedFile mappedFile = (MappedFile) mfs[i];
// 如果文件的最后修改时间大于等于参数时间
if (mappedFile.getLastModifiedTimestamp() >= timestamp) {
return mappedFile;
}
}
return (MappedFile) mfs[mfs.length - 1];
}
往上返回进入到这个方法org.apache.rocketmq.store.DefaultMessageStore#getMinPhyOffset 获取最小的物理offset
@Override
public long getMinPhyOffset() {
// 获取commitLog的最小偏移量 =》
return this.commitLog.getMinOffset();
}
进入这个方法org.apache.rocketmq.store.CommitLog#getMinOffset
public long getMinOffset() {
MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile();
if (mappedFile != null) {
if (mappedFile.isAvailable()) {
// 获取映射文件的起始偏移量
return mappedFile.getFileFromOffset();
} else {
// 获取下个文件的起始偏移量
return this.rollNextFile(mappedFile.getFileFromOffset());
}
}
return -1;
}
往上返回到这个方法org.apache.rocketmq.store.CommitLog#pickupStoreTimestamp 按offset查询存储时间
public long pickupStoreTimestamp(final long offset, final int size) {
if (offset >= this.getMinOffset()) {
// =》
SelectMappedBufferResult result = this.getMessage(offset, size);
if (null != result) {
try {
// 获取消息存储时间
return result.getByteBuffer().getLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSTION);
} finally {
result.release();
}
}
}
return -1;
}
进入这个方法org.apache.rocketmq.store.CommitLog#getMessage 获取时间、offset属性存储的映射buffer
public SelectMappedBufferResult getMessage(final long offset, final int size) {
int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMapedFileSizeCommitLog();
// 根据offset找到映射文件 =》
MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0);
if (mappedFile != null) {
int pos = (int) (offset % mappedFileSize);
return mappedFile.selectMappedBuffer(pos, size);
}
return null;
}
进入这个方法org.apache.rocketmq.store.MappedFileQueue#findMappedFileByOffset(long, boolean) 按offset获取映射文件
public MappedFile findMappedFileByOffset(final long offset, final boolean returnFirstOnNotFound) {
try {
// 获取队列中第一个映射文件
MappedFile firstMappedFile = this.getFirstMappedFile();
// 获取队列中最后一个映射文件
MappedFile lastMappedFile = this.getLastMappedFile();
if (firstMappedFile != null && lastMappedFile != null) {
// 如果offset不在索引文件的offset范围内
if (offset < firstMappedFile.getFileFromOffset() || offset >= lastMappedFile.getFileFromOffset() + this.mappedFileSize) {
LOG_ERROR.warn("Offset not matched. Request offset: {}, firstOffset: {}, lastOffset: {}, mappedFileSize: {}, mappedFiles count: {}",
offset,
firstMappedFile.getFileFromOffset(),
lastMappedFile.getFileFromOffset() + this.mappedFileSize,
this.mappedFileSize,
this.mappedFiles.size());
} else {
// 找到映射文件在队列中的索引位置
int index = (int) ((offset / this.mappedFileSize) - (firstMappedFile.getFileFromOffset() / this.mappedFileSize));
MappedFile targetFile = null;
try {
// 获取索引文件
targetFile = this.mappedFiles.get(index);
} catch (Exception ignored) {
}
// offset在目标文件的起始offset和结束offset范围内
if (targetFile != null && offset >= targetFile.getFileFromOffset()
&& offset < targetFile.getFileFromOffset() + this.mappedFileSize) {
return targetFile;
}
// 如果按索引在队列中找不到映射文件就遍历队列查找映射文件
for (MappedFile tmpMappedFile : this.mappedFiles) {
if (offset >= tmpMappedFile.getFileFromOffset()
&& offset < tmpMappedFile.getFileFromOffset() + this.mappedFileSize) {
return tmpMappedFile;
}
}
}
// 如果offset=0获取队列中第一个映射文件,个人感觉这个逻辑是否放在前面判断更为合理,还是放在这里另有深意
if (returnFirstOnNotFound) {
return firstMappedFile;
}
}
} catch (Exception e) {
log.error("findMappedFileByOffset Exception", e);
}
return null;
}
往上返回到这个方法org.apache.rocketmq.broker.processor.AdminBrokerProcessor#searchOffsetByTimestamp 解析完毕。
说在最后
本次解析仅代表个人观点,仅供参考。
加入技术微信群
钉钉技术群