/** * Copyright (C) 2010-2013 Alibaba Group Holding Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.rocketmq.store; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.rocketmq.common.UtilAll; import com.alibaba.rocketmq.common.constant.LoggerName; /** * 存储队列,数据定时删除,无限增长<br> * 队列是由多个文件组成 * * @author shijia.wxr<vintage.wang@gmail.com> * @since 2013-7-21 */ public class MapedFileQueue { private static final Logger log = LoggerFactory.getLogger(LoggerName.StoreLoggerName); // 每次触发删除文件,最多删除多少个文件 private static final int DeleteFilesBatchMax = 30; // 文件存储位置 private final String storePath; // 每个文件的大小 private final int mapedFileSize; // 各个文件 private final List<MapedFile> mapedFiles = new ArrayList<MapedFile>(); // 读写锁(针对mapedFiles) private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 预分配MapedFile对象服务 private final AllocateMapedFileService allocateMapedFileService; // 刷盘刷到哪里 /** * chen.si: 整个存储队列的下一个待写位置,抽象概念 * 由上层使用者初始化这个值,可以参考CommitLog.recoverNormally */ private long committedWhere = 0; // 最后一条消息存储时间 private volatile long storeTimestamp = 0; public MapedFileQueue(final String storePath, int mapedFileSize, AllocateMapedFileService allocateMapedFileService) { this.storePath = storePath; this.mapedFileSize = mapedFileSize; this.allocateMapedFileService = allocateMapedFileService; } public MapedFile getMapedFileByTime(final long timestamp) { Object[] mfs = this.copyMapedFiles(0); if (null == mfs) return null; /** * chen.si 从最小的0号开始找 */ for (int i = 0; i < mfs.length; i++) { MapedFile mapedFile = (MapedFile) mfs[i]; if (mapedFile.getLastModifiedTimestamp() >= timestamp) { return mapedFile; } } return (MapedFile) mfs[mfs.length - 1]; } private Object[] copyMapedFiles(final int reservedMapedFiles) { Object[] mfs = null; try { this.readWriteLock.readLock().lock(); if (this.mapedFiles.size() <= reservedMapedFiles) { return null; } mfs = this.mapedFiles.toArray(); } catch (Exception e) { e.printStackTrace(); } finally { this.readWriteLock.readLock().unlock(); } return mfs; } /** * recover时调用,不需要加锁 </br> * * chen.si: 1. offset之后的文件,全部删掉 * 2. 并且设置最后一个在用文件的writeOffset和commitOffset(这个功能实际上应该拆分出去,与方法名太不符了) * 此offset实际为 当前queue的最近的一个待写位置 */ public void truncateDirtyFiles(long offset) { List<MapedFile> willRemoveFiles = new ArrayList<MapedFile>(); for (MapedFile file : this.mapedFiles) { long fileTailOffset = file.getFileFromOffset() + this.mapedFileSize; /* * ------ ------ ------ ------ ------------------- * | F1 | | F2 | | F3 | | F4 | * ------ ------ ------ ------ ------------------- * */ /* chen.si:这里F4是最后一个文件,正常来说,当前写入位置应该处于F4文件中,所以: fileFromOffset < offset < tailOffset */ if (fileTailOffset > offset) { if (offset >= file.getFileFromOffset()) { /* * chen.si:设置F4的wrotePosition和CommitPostion, 于是处于ready状态 * 之前的Fx文件,初始化时已经都设置文件尾了 */ file.setWrotePostion((int) (offset % this.mapedFileSize)); file.setCommittedPosition((int) (offset % this.mapedFileSize)); } else { // chen.si:但是这里考虑可能出现offset < fileFromOffset,也就是 还有1个F5文件,连F4还没写完,就出现了F5 // F5认为是dirty files,所以删除掉 // chen.si:问题: 不是有预分配的情况么 --也是删除掉,不然启动时无法知道最后一个文件,还是删了干脆 // 将文件删除掉 file.destroy(1000); willRemoveFiles.add(file); } } } this.deleteExpiredFile(willRemoveFiles); } /** * 删除文件只能从头开始删 */ private void deleteExpiredFile(List<MapedFile> files) { if (!files.isEmpty()) { try { this.readWriteLock.writeLock().lock(); for (MapedFile file : files) { if (!this.mapedFiles.remove(file)) { log.error("deleteExpiredFile remove failed."); break; } } } catch (Exception e) { log.error("deleteExpiredFile has exception.", e); } finally { this.readWriteLock.writeLock().unlock(); } } } public boolean load() { File dir = new File(this.storePath); /** * chen.si: 加载所有的mapped file,其他的检验和初始化再上层的使用者recover时处理。 * 比如 CommitLog.recoverNormally * */ File[] files = dir.listFiles(); if (files != null) { // ascending order Arrays.sort(files); for (File file : files) { // 校验文件大小是否匹配 if (file.length() != this.mapedFileSize) { log.warn(file + "\t" + file.length() + " length not matched message store config value, ignore it"); return true; } // 恢复队列 try { MapedFile mapedFile = new MapedFile(file.getPath(), mapedFileSize); /** * chen.si: 先设置write和committed position为末尾 * 最后一个待写文件的这2个position会在 <code>truncateDirtyFiles</code>设置 */ mapedFile.setWrotePostion(this.mapedFileSize); mapedFile.setCommittedPosition(this.mapedFileSize); this.mapedFiles.add(mapedFile); log.info("load " + file.getPath() + " OK"); } catch (IOException e) { log.error("load file " + file + " error", e); return false; } } } return true; } /** * 刷盘进度落后了多少 */ public long howMuchFallBehind() { if (this.mapedFiles.isEmpty()) return 0; long committed = this.committedWhere; if (committed != 0) { MapedFile mapedFile = this.getLastMapedFile(); if (mapedFile != null) { /** * chen.si:最近文件的global write offset 与global committed的差 */ return (mapedFile.getFileFromOffset() + mapedFile.getWrotePostion()) - committed; } } return 0; } public MapedFile getLastMapedFile() { return this.getLastMapedFile(0); } /** * 获取最后一个MapedFile对象,如果一个都没有,则新创建一个,如果最后一个写满了,则新创建一个 * * chen.si 这里必须保证startOffset 是最后一个文件 的 offset,否则只会返回last file。这个需要上层应用控制,否则会有问题 * * @param startOffset * 如果创建新的文件,起始offset * @return */ public MapedFile getLastMapedFile(final long startOffset) { long createOffset = -1; MapedFile mapedFileLast = null; { this.readWriteLock.readLock().lock(); /** * chen.si 如果没有文件,则计算出第1个文件的起始global offset,用来创建新文件 */ if (this.mapedFiles.isEmpty()) { createOffset = startOffset - (startOffset % this.mapedFileSize); } else { /** * chen.si 直接获取最后一个元素 */ mapedFileLast = this.mapedFiles.get(this.mapedFiles.size() - 1); } this.readWriteLock.readLock().unlock(); } /** * chen.si 最后一个文件满了,则自动扩展到下一个文件,计算出下一个文件的起始global offset */ if (mapedFileLast != null && mapedFileLast.isFull()) { createOffset = mapedFileLast.getFileFromOffset() + this.mapedFileSize; } if (createOffset != -1) { String nextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset); String nextNextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset + this.mapedFileSize); MapedFile mapedFile = null; if (this.allocateMapedFileService != null) { /** * chen.si 异步分配 以及 预分配下一个文件 */ mapedFile = this.allocateMapedFileService.putRequestAndReturnMapedFile(nextFilePath, nextNextFilePath, this.mapedFileSize); } else { try { /** * chen.si 直接创建文件 */ mapedFile = new MapedFile(nextFilePath, this.mapedFileSize); } catch (IOException e) { log.error("create mapedfile exception", e); } } if (mapedFile != null) { this.readWriteLock.writeLock().lock(); if (this.mapedFiles.isEmpty()) { mapedFile.setFirstCreateInQueue(true); } this.mapedFiles.add(mapedFile); this.readWriteLock.writeLock().unlock(); } return mapedFile; } return mapedFileLast; } /** * 获取队列的最小Offset,如果队列为空,则返回-1 */ public long getMinOffset() { try { this.readWriteLock.readLock().lock(); if (!this.mapedFiles.isEmpty()) { return this.mapedFiles.get(0).getFileFromOffset(); } } catch (Exception e) { log.error("getMinOffset has exception.", e); } finally { this.readWriteLock.readLock().unlock(); } return -1; } public long getMaxOffset() { try { this.readWriteLock.readLock().lock(); if (!this.mapedFiles.isEmpty()) { int lastIndex = this.mapedFiles.size() - 1; MapedFile mapedFile = this.mapedFiles.get(lastIndex); return mapedFile.getFileFromOffset() + mapedFile.getWrotePostion(); } } catch (Exception e) { log.error("getMinOffset has exception.", e); } finally { this.readWriteLock.readLock().unlock(); } return 0; } /** * 恢复时调用 */ public void deleteLastMapedFile() { if (!this.mapedFiles.isEmpty()) { int lastIndex = this.mapedFiles.size() - 1; MapedFile mapedFile = this.mapedFiles.get(lastIndex); mapedFile.destroy(1000); this.mapedFiles.remove(mapedFile); log.info("on recover, destroy a logic maped file " + mapedFile.getFileName()); } } /** * 根据文件过期时间来删除物理队列文件 */ public int deleteExpiredFileByTime(// final long expiredTime, // final int deleteFilesInterval, // final long intervalForcibly,// final boolean cleanImmediately// ) { Object[] mfs = this.copyMapedFiles(0); if (null == mfs) return 0; // 最后一个文件处于写状态,不能删除 int mfsLength = mfs.length - 1; int deleteCount = 0; /** * chen.si TODO 先删物理文件,然后才从queue里删除,不会出问题吗? */ List<MapedFile> files = new ArrayList<MapedFile>(); if (null != mfs) { for (int i = 0; i < mfsLength; i++) { MapedFile mapedFile = (MapedFile) mfs[i]; long liveMaxTimestamp = mapedFile.getLastModifiedTimestamp() + expiredTime; if (System.currentTimeMillis() >= liveMaxTimestamp// || cleanImmediately) { if (mapedFile.destroy(intervalForcibly)) { files.add(mapedFile); deleteCount++; if (files.size() >= DeleteFilesBatchMax) { break; } if (deleteFilesInterval > 0 && (i + 1) < mfsLength) { try { Thread.sleep(deleteFilesInterval); } catch (InterruptedException e) { e.printStackTrace(); } } } else { break; } } } } deleteExpiredFile(files); return deleteCount; } /** * 根据物理队列最小Offset来删除逻辑队列 * * @param offset * 物理队列最小offset */ public int deleteExpiredFileByOffset(long offset, int unitSize) { Object[] mfs = this.copyMapedFiles(0); /** * chen.si 这里的offset,是数据文件(commit log)的第1个消息的phy offset */ List<MapedFile> files = new ArrayList<MapedFile>(); int deleteCount = 0; if (null != mfs) { // 最后一个文件处于写状态,不能删除 int mfsLength = mfs.length - 1; // 这里遍历范围 0 ... last - 1 for (int i = 0; i < mfsLength; i++) { boolean destroy = true; MapedFile mapedFile = (MapedFile) mfs[i]; SelectMapedBufferResult result = mapedFile.selectMapedBuffer(this.mapedFileSize - unitSize); if (result != null) { /** * chen.si 找到最后一条消息,其offset肯定是最大的 。如果最大的都比 传入的offset 小,则这个索引文件已经没用了,可以删掉了。 * * 如果最大的 比 传入的offset 大, 说明这个文件还有有效消息,不能删 */ long maxOffsetInLogicQueue = result.getByteBuffer().getLong(); result.release(); // 当前文件是否可以删除 destroy = (maxOffsetInLogicQueue < offset); if (destroy) { log.info("physic min offset " + offset + ", logics in current mapedfile max offset " + maxOffsetInLogicQueue + ", delete it"); } } else { log.warn("this being not excuted forever."); break; } if (destroy && mapedFile.destroy(1000 * 60)) { files.add(mapedFile); deleteCount++; } else { break; } } } deleteExpiredFile(files); return deleteCount; } /** * 返回值表示是否全部刷盘完成 * * @return */ public boolean commit(final int flushLeastPages) { boolean result = true; MapedFile mapedFile = this.findMapedFileByOffset(this.committedWhere, true); if (mapedFile != null) { long tmpTimeStamp = mapedFile.getStoreTimestamp(); int offset = mapedFile.commit(flushLeastPages); long where = mapedFile.getFileFromOffset() + offset; /** * chen.si 关键点,通过commit完成后的where 与 committedWhere比较,以判断消息是否全部刷盘成功 */ result = (where == this.committedWhere); this.committedWhere = where; /** * chen.si TODO 看不明白,满足X页了 或者 文件满,进行了commit,为什么不更新storeTimestamp */ if (0 == flushLeastPages) { this.storeTimestamp = tmpTimeStamp; } } return result; } /** * chen.si 根据offset,找到offset所在的文件 * * @param offset * @param returnFirstOnNotFound * @return */ public MapedFile findMapedFileByOffset(final long offset, final boolean returnFirstOnNotFound) { try { this.readWriteLock.readLock().lock(); MapedFile mapedFile = this.getFirstMapedFile(); if (mapedFile != null) { int index = (int) ((offset / this.mapedFileSize) - (mapedFile.getFileFromOffset() / this.mapedFileSize)); if (index < 0 || index >= this.mapedFiles.size()) { log.warn("findMapedFileByOffset offset not matched, request Offset: " + offset + ", index: " + index + ", mapedFileSize: " + this.mapedFileSize + ", mapedFiles count: " + this.mapedFiles.size()); } try { return this.mapedFiles.get(index); } catch (Exception e) { if (returnFirstOnNotFound) { return mapedFile; } } } } catch (Exception e) { e.printStackTrace(); } finally { this.readWriteLock.readLock().unlock(); } return null; } private MapedFile getFirstMapedFile() { if (this.mapedFiles.isEmpty()) { return null; } return this.mapedFiles.get(0); } public MapedFile getLastMapedFile2() { if (this.mapedFiles.isEmpty()) { return null; } return this.mapedFiles.get(this.mapedFiles.size() - 1); } public MapedFile findMapedFileByOffset(final long offset) { return findMapedFileByOffset(offset, false); } public long getMapedMemorySize() { long size = 0; Object[] mfs = this.copyMapedFiles(0); if (mfs != null) { for (Object mf : mfs) { if (((ReferenceResource) mf).isAvailable()) { size += this.mapedFileSize; } } } return size; } public boolean retryDeleteFirstFile(final long intervalForcibly) { MapedFile mapedFile = this.getFirstMapedFileOnLock(); if (mapedFile != null) { if (!mapedFile.isAvailable()) { log.warn("the mapedfile was destroyed once, but still alive, " + mapedFile.getFileName()); boolean result = mapedFile.destroy(intervalForcibly); if (result) { log.warn("the mapedfile redelete OK, " + mapedFile.getFileName()); List<MapedFile> tmps = new ArrayList<MapedFile>(); tmps.add(mapedFile); this.deleteExpiredFile(tmps); } else { log.warn("the mapedfile redelete Failed, " + mapedFile.getFileName()); } return result; } } return false; } public MapedFile getFirstMapedFileOnLock() { try { this.readWriteLock.readLock().lock(); return this.getFirstMapedFile(); } finally { this.readWriteLock.readLock().unlock(); } } /** * 关闭队列,队列数据还在,但是不能访问 */ public void shutdown(final long intervalForcibly) { this.readWriteLock.readLock().lock(); for (MapedFile mf : this.mapedFiles) { mf.shutdown(intervalForcibly); } this.readWriteLock.readLock().unlock(); } /** * 销毁队列,队列数据被删除,此函数有可能不成功 */ public void destroy() { this.readWriteLock.writeLock().lock(); for (MapedFile mf : this.mapedFiles) { mf.destroy(1000 * 3); } this.mapedFiles.clear(); this.committedWhere = 0; this.readWriteLock.writeLock().unlock(); } public long getCommittedWhere() { return committedWhere; } public void setCommittedWhere(long committedWhere) { this.committedWhere = committedWhere; } public long getStoreTimestamp() { return storeTimestamp; } public List<MapedFile> getMapedFiles() { return mapedFiles; } public int getMapedFileSize() { return mapedFileSize; } }