rocketmq源码解析之管理请求获取topic的状态信息

说在前面

管理请求 GET_TOPIC_STATS_INFO 获取topic的状态信息

 

源码解析

进入这个方法org.apache.rocketmq.broker.processor.AdminBrokerProcessor#getTopicStatsInfo获取topic的状态信息

private RemotingCommand getTopicStatsInfo(ChannelHandlerContext ctx,
        RemotingCommand request) throws RemotingCommandException {
        final RemotingCommand response = RemotingCommand.createResponseCommand(null);
        final GetTopicStatsInfoRequestHeader requestHeader =
            (GetTopicStatsInfoRequestHeader) request.decodeCommandCustomHeader(GetTopicStatsInfoRequestHeader.class);
        final String topic = requestHeader.getTopic();
//        从topic配置信息缓存中获取topic配置信息
        TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic);
        if (null == topicConfig) {
            response.setCode(ResponseCode.TOPIC_NOT_EXIST);
            response.setRemark("topic[" + topic + "] not exist");
            return response;
        }

//        组装topic状态信息
        TopicStatsTable topicStatsTable = new TopicStatsTable();
        for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) {
            MessageQueue mq = new MessageQueue();
            mq.setTopic(topic);
            mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName());
            mq.setQueueId(i);
//            组装topic的offset信息
            TopicOffset topicOffset = new TopicOffset();
//            根据topic和queueId查询消息队列最小offset=》
            long min = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, i);
            if (min < 0)
                min = 0;
//            根据topic和queueId查询消息队列的最大offset=》
            long max = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i);
            if (max < 0)
                max = 0;
            long timestamp = 0;
            if (max > 0) {
//                根据topic、queueId,offset查询offset存储的时间=》
                timestamp = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, max - 1);
            }

            topicOffset.setMinOffset(min);
            topicOffset.setMaxOffset(max);
            topicOffset.setLastUpdateTimestamp(timestamp);
            topicStatsTable.getOffsetTable().put(mq, topicOffset);
        }

        byte[] body = topicStatsTable.encode();
        response.setBody(body);
        response.setCode(ResponseCode.SUCCESS);
        response.setRemark(null);
        return response;
    }

进入这个方法org.apache.rocketmq.store.DefaultMessageStore#getMinOffsetInQueue 按topic、queueId查询消费队列的最小的offset

public long getMinOffsetInQueue(String topic, int queueId) {
//        根据topic和queueId查询消费者队列 =》
        ConsumeQueue logic = this.findConsumeQueue(topic, queueId);
        if (logic != null) {
//            获取队列中的最小offset
            return logic.getMinOffsetInQueue();
        }

        return -1;
    }

进入这个方法org.apache.rocketmq.store.DefaultMessageStore#findConsumeQueue 按topic、queueId查询到消费队列

public ConsumeQueue findConsumeQueue(String topic, int queueId) {
//        找到topic的所有消息队列
        ConcurrentMap map = consumeQueueTable.get(topic);
        if (null == map) {
            ConcurrentMap newMap = new ConcurrentHashMap(128);
            ConcurrentMap 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.DefaultMessageStore#getMaxOffsetInQueue 按topic、queueId查询消费队列的最大offset

public long getMaxOffsetInQueue(String topic, int queueId) {
//        根据topic和queueId找到消费者队列=》
        ConsumeQueue logic = this.findConsumeQueue(topic, queueId);
        if (logic != null) {
//            获取最大的offset =》
            long offset = logic.getMaxOffsetInQueue();
            return offset;
        }

//        如果不存在指定topic和queueId的消费队列直接返回0
        return 0;
    }

进入这个方法org.apache.rocketmq.store.DefaultMessageStore#findConsumeQueue 按topic、queueId查询消费队列,前面介绍过

public ConsumeQueue findConsumeQueue(String topic, int queueId) {
//        找到topic的所有消息队列
        ConcurrentMap map = consumeQueueTable.get(topic);
        if (null == map) {
            ConcurrentMap newMap = new ConcurrentHashMap(128);
            ConcurrentMap 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#getMaxOffsetInQueue查询最大的offset

public long getMaxOffsetInQueue() {
//        =》
        return this.mappedFileQueue.getMaxOffset() / CQ_STORE_UNIT_SIZE;
    }

进入到这个方法

org.apache.rocketmq.store.MappedFileQueue#getMaxOffset

public long getMaxOffset() {
//        获取存储映射文件队列中索引位置最大的映射文件=》
        MappedFile mappedFile = getLastMappedFile();
        if (mappedFile != null) {
//            映射文件的起始offset+映射文件的可读取的索引位置
            return mappedFile.getFileFromOffset() + mappedFile.getReadPosition();
        }
//        如果队列中没有存储映射文件直接返回0
        return 0;
    }

进入到这个方法

org.apache.rocketmq.store.MappedFileQueue#getLastMappedFile()查询映射文件队列中的最后一个映射文件

public MappedFile getLastMappedFile() {
    MappedFile mappedFileLast = null;
    while (!this.mappedFiles.isEmpty()) {
        try {
            mappedFileLast = this.mappedFiles.get(this.mappedFiles.size() - 1);
            break;
        } catch (IndexOutOfBoundsException e) {
            //continue;
        } catch (Exception e) {
            log.error("getLastMappedFile has exception.", e);
            break;
        }
    }

    return mappedFileLast;
}

可以看到这里线程安全的队列实现是CopyOnWriteArrayList,这个数据结构是不可变模式无锁实现,读操作大于写操作时候效率很高

//    并发线程安全队列存储映射文件
    private final CopyOnWriteArrayList mappedFiles = new CopyOnWriteArrayList();

往上返回到这个方法

org.apache.rocketmq.store.DefaultMessageStore#getMessageStoreTimeStamp 按topic、queueId、offset查询offset的存储时间

@Override
    public long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) {
//        按topic和queueId查询到消费队列=》
        ConsumeQueue logicQueue = this.findConsumeQueue(topic, queueId);
        if (logicQueue != null) {
//            按消费者的offset查询存储时间所在的buffer=》
            SelectMappedBufferResult result = logicQueue.getIndexBuffer(consumeQueueOffset);
//            =》
            return getStoreTime(result);
        }

        return -1;
    }

进入这个方法

org.apache.rocketmq.store.DefaultMessageStore#findConsumeQueue 按topic、queueId查询消费队列,前面介绍过

public ConsumeQueue findConsumeQueue(String topic, int queueId) {
//        找到topic的所有消息队列
        ConcurrentMap map = consumeQueueTable.get(topic);
        if (null == map) {
            ConcurrentMap newMap = new ConcurrentHashMap(128);
            ConcurrentMap 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#getIndexBuffer 按索引查询映射buffer

public SelectMappedBufferResult getIndexBuffer(final long startIndex) {
        int mappedFileSize = this.mappedFileSize;
//        获取最小的物理offset
        long offset = startIndex * CQ_STORE_UNIT_SIZE;
        if (offset >= this.getMinLogicOffset()) {
//            根据offset查询映射文件 =》
            MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset);
            if (mappedFile != null) {
                SelectMappedBufferResult result = mappedFile.selectMappedBuffer((int) (offset % mappedFileSize));
                return result;
            }
        }
        return null;
    }

进入这个方法

org.apache.rocketmq.store.MappedFileQueue#findMappedFileByOffset(long) 按offset查询映射文件

public MappedFile findMappedFileByOffset(final long offset) {
//        =》
        return findMappedFileByOffset(offset, false);
    }

进入这个方法org.apache.rocketmq.store.MappedFileQueue#findMappedFileByOffset(long, boolean)

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.store.DefaultMessageStore#getStoreTime 根据SelectMappedBufferResult查询存储时间

private long getStoreTime(SelectMappedBufferResult result) {
        if (result != null) {
            try {
                final long phyOffset = result.getByteBuffer().getLong();
                final int size = result.getByteBuffer().getInt();
//                根据SelectMappedBufferResult的offset和大小查找存储时间=》
                long storeTime = this.getCommitLog().pickupStoreTimestamp(phyOffset, size);
                return storeTime;
            } catch (Exception e) {
            } finally {
                result.release();
            }
        }
        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、大小查询SelectMappedBufferResult

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#getTopicStatsInfo结束

 

说在最后

本次解析仅代表个人观点,仅供参考。

 

加入技术微信群

rocketmq源码解析之管理请求获取topic的状态信息_第1张图片

钉钉技术群

rocketmq源码解析之管理请求获取topic的状态信息_第2张图片

转载于:https://my.oschina.net/u/3775437/blog/3094111

你可能感兴趣的:(rocketmq源码解析之管理请求获取topic的状态信息)