说在前面
管理请求 INVOKE_BROKER_TO_RESET_OFFSET 重置broker offset
源码解析
进入这个方法org.apache.rocketmq.broker.processor.AdminBrokerProcessor#resetOffset 重置broker offset
public RemotingCommand resetOffset(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final ResetOffsetRequestHeader requestHeader = (ResetOffsetRequestHeader) request.decodeCommandCustomHeader(ResetOffsetRequestHeader.class); log.info("[reset-offset] reset offset started by {}. topic={}, group={}, timestamp={}, isForce={}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.getTopic(), requestHeader.getGroup(), requestHeader.getTimestamp(), requestHeader.isForce()); boolean isC = false; LanguageCode language = request.getLanguage(); switch (language) { case CPP: isC = true; break; } // =》 return this.brokerController.getBroker2Client().resetOffset(requestHeader.getTopic(), requestHeader.getGroup(), requestHeader.getTimestamp(), requestHeader.isForce(), isC); }
进入这个方法org.apache.rocketmq.broker.client.net.Broker2Client#resetOffset(java.lang.String, java.lang.String, long, boolean, boolean)重置offset
public RemotingCommand resetOffset(String topic, String group, long timeStamp, boolean isForce, boolean isC) { final RemotingCommand response = RemotingCommand.createResponseCommand(null); // 按topic从缓存中获取topic配置信息=》 TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic); if (null == topicConfig) { log.error("[reset-offset] reset offset failed, no topic in this broker. topic={}", topic); response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("[reset-offset] reset offset failed, no topic in this broker. topic=" + topic); return response; } MapoffsetTable = new HashMap (); for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) { MessageQueue mq = new MessageQueue(); mq.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName()); mq.setTopic(topic); mq.setQueueId(i); // 查询消费者offset=》 long consumerOffset = this.brokerController.getConsumerOffsetManager().queryOffset(group, topic, i); if (-1 == consumerOffset) { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("THe consumer group <%s> not exist", group)); return response; } long timeStampOffset; if (timeStamp == -1) { // 按topic和queueId查询到最大offset=》 timeStampOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, i); } else { // 按时间、topic、queueId查询时间offset=》 timeStampOffset = this.brokerController.getMessageStore().getOffsetInQueueByTime(topic, i, timeStamp); } if (timeStampOffset < 0) { log.warn("reset offset is invalid. topic={}, queueId={}, timeStampOffset={}", topic, i, timeStampOffset); timeStampOffset = 0; } if (isForce || timeStampOffset < consumerOffset) { // 获取比较小的offset offsetTable.put(mq, timeStampOffset); } else { offsetTable.put(mq, consumerOffset); } } ResetOffsetRequestHeader requestHeader = new ResetOffsetRequestHeader(); requestHeader.setTopic(topic); requestHeader.setGroup(group); requestHeader.setTimestamp(timeStamp); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESET_CONSUMER_CLIENT_OFFSET, requestHeader); if (isC) { // c++ language ResetOffsetBodyForC body = new ResetOffsetBodyForC(); // 转换offset集合=》 List offsetList = convertOffsetTable2OffsetList(offsetTable); body.setOffsetTable(offsetList); request.setBody(body.encode()); } else { // other language ResetOffsetBody body = new ResetOffsetBody(); body.setOffsetTable(offsetTable); request.setBody(body.encode()); } // 从缓存中获取消费组信息 ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(group); // 消费组缓存的client channel if (consumerGroupInfo != null && !consumerGroupInfo.getAllChannel().isEmpty()) { ConcurrentMap channelInfoTable = consumerGroupInfo.getChannelInfoTable(); for (Map.Entry entry : channelInfoTable.entrySet()) { int version = entry.getValue().getVersion(); if (version >= MQVersion.Version.V3_0_7_SNAPSHOT.ordinal()) { try { // 重置offset请求=》 this.brokerController.getRemotingServer().invokeOneway(entry.getKey(), request, 5000); log.info("[reset-offset] reset offset success. topic={}, group={}, clientId={}", topic, group, entry.getValue().getClientId()); } catch (Exception e) { log.error("[reset-offset] reset offset exception. topic={}, group={}", new Object[] {topic, group}, e); } } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("the client does not support this feature. version=" + MQVersion.getVersionDesc(version)); log.warn("[reset-offset] the client does not support this feature. version={}", RemotingHelper.parseChannelRemoteAddr(entry.getKey()), MQVersion.getVersionDesc(version)); return response; } } } else { String errorInfo = String.format("Consumer not online, so can not reset offset, Group: %s Topic: %s Timestamp: %d", requestHeader.getGroup(), requestHeader.getTopic(), requestHeader.getTimestamp()); log.error(errorInfo); response.setCode(ResponseCode.CONSUMER_NOT_ONLINE); response.setRemark(errorInfo); return response; } response.setCode(ResponseCode.SUCCESS); ResetOffsetBody resBody = new ResetOffsetBody(); resBody.setOffsetTable(offsetTable); response.setBody(resBody.encode()); return response; }
进入这个方法org.apache.rocketmq.broker.topic.TopicConfigManager#selectTopicConfig按topic获取topic配置
public TopicConfig selectTopicConfig(final String topic) { // 从topic配置缓存信息中查询当前topic的配置 return this.topicConfigTable.get(topic); }
进入这个方法 按group, 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; ConcurrentMapmap = this.offsetTable.get(key); if (null != map) { Long offset = map.get(queueId); if (offset != null) return offset; } return -1; }
进入这个方法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的所有消息队列 ConcurrentMapmap = 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; }
往上返回到这个方法org.apache.rocketmq.store.DefaultMessageStore#getOffsetInQueueByTime 按topic、queueId、timestamp查询timeStampOffset
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按topic、queueId查询消费队列
public ConsumeQueue findConsumeQueue(String topic, int queueId) { // 找到topic的所有消息队列 ConcurrentMapmap = 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#getOffsetInQueueByTime
按timestamp查询timeStampOffset
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按timestamp查询MappedFile
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擦查询minPhyOffset
@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.MappedFileQueue#getFirstMappedFile找到映射文件队列中第一个映射文件
public MappedFile getFirstMappedFile() { MappedFile mappedFileFirst = null; if (!this.mappedFiles.isEmpty()) { try { mappedFileFirst = this.mappedFiles.get(0); } catch (IndexOutOfBoundsException e) { //ignore } catch (Exception e) { log.error("getFirstMappedFile has exception.", e); } } return mappedFileFirst; }
进入这个方法org.apache.rocketmq.store.CommitLog#rollNextFile获取下一个映射文件的起始偏移量
public long rollNextFile(final long offset) { int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMapedFileSizeCommitLog(); return offset + mappedFileSize - offset % mappedFileSize; }
往上返回到这个方法org.apache.rocketmq.store.MappedFile#selectMappedBuffer(int)查询SelectMappedBufferResult
public SelectMappedBufferResult selectMappedBuffer(int pos) { // 获取文件读取的位置 int readPosition = getReadPosition(); if (pos < readPosition && pos >= 0) { if (this.hold()) { // 创建新的缓冲区 ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); byteBuffer.position(pos); int size = readPosition - pos; ByteBuffer byteBufferNew = byteBuffer.slice(); byteBufferNew.limit(size); return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); } } return null; }
往上返回到这个方法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查询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查询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.remoting.netty.NettyRemotingServer#invokeOneway执行单途rpc请求
@Override public void invokeOneway(Channel channel, RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { this.invokeOnewayImpl(channel, request, timeoutMillis); }
进入这个方法org.apache.rocketmq.remoting.netty.NettyRemotingAbstract#invokeOnewayImpl
public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { request.markOnewayRPC(); // 获取信号量的信号 boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS); if (acquired) { final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway); try { channel.writeAndFlush(request).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { once.release(); if (!f.isSuccess()) { log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed."); } } }); } catch (Exception e) { once.release(); log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed."); throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e); } } else { if (timeoutMillis <= 0) { throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast"); } else { String info = String.format( "invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d", timeoutMillis, this.semaphoreOneway.getQueueLength(), this.semaphoreOneway.availablePermits() ); log.warn(info); throw new RemotingTimeoutException(info); } } }
往上返回到这个方法org.apache.rocketmq.broker.processor.AdminBrokerProcessor#resetOffset结束
说在最后
本次解析仅代表个人观点,仅供参考。
加入技术微信群
钉钉技术群