说在前面
管理请求 GET_CONSUME_STATS 获取消费者状态
源码解析
进入到这个方法org.apache.rocketmq.broker.processor.AdminBrokerProcessor#getConsumeStats获取消费者状态
private RemotingCommand getConsumeStats(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final GetConsumeStatsRequestHeader requestHeader = (GetConsumeStatsRequestHeader) request.decodeCommandCustomHeader(GetConsumeStatsRequestHeader.class); ConsumeStats consumeStats = new ConsumeStats(); Set<String> topics = new HashSet<String>(); if (UtilAll.isBlank(requestHeader.getTopic())) { // 获取消费者所在组的topics=》 topics = this.brokerController.getConsumerOffsetManager().whichTopicByConsumer(requestHeader.getConsumerGroup()); } else { topics.add(requestHeader.getTopic()); } for (String topic : topics) { // 按topic名称从本地缓存中获取topic配置信息 TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { log.warn("consumeStats, topic config not exist, {}", topic); continue; } { // 按消费组和topic获取订阅数据=》 SubscriptionData findSubscriptionData = this.brokerController.getConsumerManager().findSubscriptionData(requestHeader.getConsumerGroup(), topic); if (null == findSubscriptionData && this.brokerController.getConsumerManager().findSubscriptionDataCount(requestHeader.getConsumerGroup()) > 0) { log.warn("consumeStats, the consumer group[{}], topic[{}] not exist", requestHeader.getConsumerGroup(), topic); continue; } } // 默认读队列数量16 for (int i = 0; i < topicConfig.getReadQueueNums(); i++) { MessageQueue mq = new MessageQueue(); mq.setTopic(topic); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setQueueId(i); OffsetWrapper offsetWrapper = new OffsetWrapper(); // =》按topic和queueId查询broker的offset long brokerOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); if (brokerOffset < 0) brokerOffset = 0; // 按消费组、topic、queueId查询消费者的offset=》 long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset( requestHeader.getConsumerGroup(), topic, i); if (consumerOffset < 0) consumerOffset = 0; offsetWrapper.setBrokerOffset(brokerOffset); offsetWrapper.setConsumerOffset(consumerOffset); long timeOffset = consumerOffset - 1; if (timeOffset >= 0) { // 查询最后时间 =》 long lastTimestamp = this.brokerController.getMessageStore().getMessageStoreTimeStamp(topic, i, timeOffset); if (lastTimestamp > 0) { offsetWrapper.setLastTimestamp(lastTimestamp); } } consumeStats.getOffsetTable().put(mq, offsetWrapper); } // 按消费组和topic获取消费者的tps double consumeTps = this.brokerController.getBrokerStatsManager().tpsGroupGetNums(requestHeader.getConsumerGroup(), topic); consumeTps += consumeStats.getConsumeTps(); consumeStats.setConsumeTps(consumeTps); } byte[] body = consumeStats.encode(); response.setBody(body); response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; }
进入到这个方法org.apache.rocketmq.broker.offset.ConsumerOffsetManager#whichTopicByConsumer找到消费组订阅的topic
public Set<String> whichTopicByConsumer(final String group) { Set<String> topics = new HashSet<String>(); // 遍历消费者组、topic缓存信息 Iterator<Entry<String, ConcurrentMap<Integer, Long>>> it = this.offsetTable.entrySet().iterator(); while (it.hasNext()) { Entry<String, ConcurrentMap<Integer, Long>> next = it.next(); String topicAtGroup = next.getKey(); String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR); if (arrays.length == 2) { if (group.equals(arrays[1])) { topics.add(arrays[0]); } } } return topics; }
进入这个方法org.apache.rocketmq.broker.topic.TopicConfigManager#selectTopicConfig从topic配置信息中获取当前topic的配置
public TopicConfig selectTopicConfig(final String topic) { // 从topic配置缓存信息中查询当前topic的配置 return this.topicConfigTable.get(topic); }
进入这个方法org.apache.rocketmq.broker.client.ConsumerManager#findSubscriptionData按消费组、topic查询消息组订阅数据信息
public SubscriptionData findSubscriptionData(final String group, final String topic) { // 按组从缓存中获取消费组信息 ConsumerGroupInfo consumerGroupInfo = this.getConsumerGroupInfo(group); if (consumerGroupInfo != null) { // 按topic从缓存中获取订阅数据 return consumerGroupInfo.findSubscriptionData(topic); } return null; }
进入这个方法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<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#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<MappedFile> mappedFiles = new CopyOnWriteArrayList<MappedFile>();
往上返回进入这个方法org.apache.rocketmq.broker.offset.ConsumerOffsetManager#queryOffset(java.lang.String, java.lang.String, int) 按消费组、topic、queueId查询消费者的offset
public long queryOffset(final String group, final String topic, final int queueId) { // topic@group 从本地offset缓存中查询 String key = topic + TOPIC_GROUP_SEPARATOR + group; ConcurrentMap<Integer, Long> map = this.offsetTable.get(key); if (null != map) { Long offset = map.get(queueId); if (offset != null) return offset; } return -1; }
进入这个方法org.apache.rocketmq.store.DefaultMessageStore#getMessageStoreTimeStamp 按topic、queueId、consumeQueueOffset查询消费者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<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#getIndexBuffer按consumeQueueOffset查询SelectMappedBufferResult
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, boolean)按consumeQueueOffset查询MappedFile
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查询MappedFile
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#getConsumeStats结束
说在最后
本次解析仅代表个人观点,仅供参考。
加入技术微信群
钉钉技术群