RocketMq消息存储-MappedFileQueue

上一篇我们分析介绍了RocketMq利用MappedFile对一个文件的操作,这里我们继续分析RocketMq如何管理文件集。

MappedFileQueue就是RocketMq封装的对有序文件队列的管理,其主要用于CommitLog,ConsumeQueue,IndexFile集合的操作管理。

MappedFileQueue相关属性

public class MappedFileQueue {

    // 批量删除文件最大数量
    private static final int DELETE_FILES_BATCH_MAX = 10;

    // 存儲路徑
    private final String storePath;

    // 目录下每个文件的大小 (commitLog:1G,consumeQueue:30w*20bytes, indexFile:400M 约2000w数据)
    protected final int mappedFileSize;

    // 目录下的mappedFile对象集合
    protected final CopyOnWriteArrayList<MappedFile> mappedFiles = new CopyOnWriteArrayList<MappedFile>();

    // 创建mappedFile的服务,内部有自己的线程 咱们通过向它提交request 内部线程处理完后会返回结果,就是mappedFile对象
    private final AllocateMappedFileService allocateMappedFileService;

    // 当前目录下,刷盘位点(currentMappedFile.name + wrotePosition)
    protected long flushedWhere = 0;
    // 当前目录下的
    private long committedWhere = 0;

    // 最后一条消息的存储时间
    private volatile long storeTimestamp = 0;

    public MappedFileQueue(final String storePath, int mappedFileSize,
        AllocateMappedFileService allocateMappedFileService) {
        this.storePath = storePath;
        this.mappedFileSize = mappedFileSize;
        this.allocateMappedFileService = allocateMappedFileService;
    }
}
  1. storePath定义了管理文件集路径

  1. mappedFileSize定义了单个文件的大小

  1. mappedFiles管理关联着文件夹下所有的文件

其中定义了多个比较重要的方法

MappedFileQueue# load()加载方法
MappedFileQueue# flush(int)} 刷盘
MappedFileQueue# getLastMappedFile() 当前正在写入的mappedFile,不存在走创建逻辑
MappedFileQueue# findMappedFileByOffset(long, boolean) 根据offset查找目标mappedFile对象
MappedFileQueue# deleteExpiredFileByTime(long, int, long, boolean) 根据offset查找目标mappedFile对象 commitLog删除逻辑
加载

遍历文件夹,将所有文件的链接封装为MappedFile对象放入mappedFilesList对象中。

public boolean load() {
    File dir = new File(this.storePath);
    File[] ls = dir.listFiles();
    if (ls != null) {
        // 遍历
        return doLoad(Arrays.asList(ls));
    }
    return true;
}

public boolean doLoad(List<File> files) {
    // ascending order
    files.sort(Comparator.comparing(File::getName));

    // 迭代
    for (File file : files) {
        if (file.length() != this.mappedFileSize) {
            log.warn(file + "\t" + file.length() + " length not matched message store config value, please check it manually");
            return false;
        }
        try {
            // 建立mappedFile对象
            MappedFile mappedFile = new MappedFile(file.getPath(), mappedFileSize);
            // 添加到管理集合
            this.mappedFiles.add(mappedFile);
            log.info("load " + file.getPath() + " OK");
        } catch (IOException e) {
            log.error("load file " + file + " error", e);
            return false;
        }
    }
    return true;
}
获取末尾的文件
public MappedFile getLastMappedFile(final long startOffset, boolean needCreate) {
    // 文件起始偏移量
    long createOffset = -1;
    // 先尝试获取
    MappedFile mappedFileLast = getLastMappedFile();

    if (mappedFileLast == null) {
        // 没有获取到
        createOffset = startOffset - (startOffset % this.mappedFileSize);
    }

    // 存在 但是满了
    if (mappedFileLast != null && mappedFileLast.isFull()) {
        createOffset = mappedFileLast.getFileFromOffset() + this.mappedFileSize;
    }

    // 创建新文件
    // 1.list为空
    // 2.最近的文件写满了
    if (createOffset != -1 && needCreate) {
        return tryCreateMappedFile(createOffset);
    }

    return mappedFileLast;
}

文件不存在就创建tryCreateMappedFile

protected MappedFile tryCreateMappedFile(long createOffset) {
    // 文件名
    String nextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset);
//
String nextNextFilePath = this.storePath + File.separator + UtilAll.offset2FileName(createOffset
                                                                                    + this.mappedFileSize);
// do create
return doCreateMappedFile(nextFilePath, nextNextFilePath);
}


protected MappedFile doCreateMappedFile(String nextFilePath, String nextNextFilePath) {
    MappedFile mappedFile = null;

    if (this.allocateMappedFileService != null) {
        // 用allocateMappedFileService创建
        // 当mappedfileSize>1G 创建会预热,在空页上写零值,物理页就会先创建出来
        mappedFile = this.allocateMappedFileService
            .putRequestAndReturnMappedFile(nextFilePath,nextNextFilePath, this.mappedFileSize);
    } else {
        try {
            // 直接new
            mappedFile = new MappedFile(nextFilePath, this.mappedFileSize);
        } catch (IOException e) {
            log.error("create mappedFile exception", e);
        }
    }
    if (mappedFile != null) {
        //
        if (this.mappedFiles.isEmpty()) {
            // 目录下的首文件
            mappedFile.setFirstCreateInQueue(true);
        }
        // 添加到目录list
        this.mappedFiles.add(mappedFile);
    }
    return mappedFile;
}

获取文件操作,隐含着创建文件的逻辑。

刷盘操作
/**
*
* @param flushLeastPages 0:表示强制刷新 >0:表示脏页达到flushLeastPages才刷脏
* @return true:实际没刷数据,false:实际刷了数据
*/
public boolean flush(final int flushLeastPages) {
    boolean result = true;
    //
    MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
    if (mappedFile != null) {
        // 找到了 刷盘
        long tmpTimeStamp = mappedFile.getStoreTimestamp();
        // 刷盘
        int offset = mappedFile.flush(flushLeastPages);
        // 更新刷盘位点
        long where = mappedFile.getFileFromOffset() + offset;
        // true:实际没刷数据,false:实际刷了数据
        result = where == this.flushedWhere;
        this.flushedWhere = where;

        if (0 == flushLeastPages) {
            this.storeTimestamp = tmpTimeStamp;
        }
    }

    return result;
}
提交
public boolean commit(final int commitLeastPages) {
    boolean result = true;
    // 找到上次提交的文件
    MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, this.committedWhere == 0);
    if (mappedFile != null) {
        int offset = mappedFile.commit(commitLeastPages);
        long where = mappedFile.getFileFromOffset() + offset;
        result = where == this.committedWhere;
        this.committedWhere = where;
    }
    return result;
}

这里看到有提交消息就返回false

清理文件
/**
* consumeQueue删除逻辑
* @param offset offset
* @param unitSize
* @return
*/
public int deleteExpiredFileByOffset(long offset, int unitSize) {
    // 拷贝
    Object[] mfs = this.copyMappedFiles(0);

    //
    List<MappedFile> files = new ArrayList<MappedFile>();
    int deleteCount = 0;
    if (null != mfs) {

        // 保证正在写的数据不被删除
        int mfsLength = mfs.length - 1;

        //
        for (int i = 0; i < mfsLength; i++) {
            boolean destroy;
            MappedFile mappedFile = (MappedFile) mfs[i];
            // 当前文件的最后一个数据单元
            SelectMappedBufferResult result = mappedFile.selectMappedBuffer(this.mappedFileSize - unitSize);
            if (result != null) {
                // 最后一条数据的commitLog物理偏移量
                long maxOffsetInLogicQueue = result.getByteBuffer().getLong();
                result.release();
                // true:当前queue都是过期数据
                destroy = maxOffsetInLogicQueue < offset;
                if (destroy) {
                    log.info("physic min offset " + offset + ", logics in current mappedFile max offset "
                             + maxOffsetInLogicQueue + ", delete it");
                }
            } else if (!mappedFile.isAvailable()) { // Handle hanged file.
                log.warn("Found a hanged consume queue file, attempting to delete it.");
                destroy = true;
            } else {
                log.warn("this being not executed forever.");
                break;
            }

            if (destroy && mappedFile.destroy(1000 * 60)) {
                // 删除记录
                files.add(mappedFile);
                // 删除量
                deleteCount++;
            } else {
                break;
            }
        }
    }

    deleteExpiredFile(files);

    return deleteCount;
}


/**
* commitLog删除逻辑
* @param expiredTime         过期时间
* @param deleteFilesInterval 删除两个文件直接的时间间隔
* @param intervalForcibly    mf.destory传递的参数
* @param cleanImmediately    强制删除
* @return
*/
public int deleteExpiredFileByTime(final long expiredTime,
                                   final int deleteFilesInterval,
                                   final long intervalForcibly,
                                   final boolean cleanImmediately) {
    // 拷贝
    Object[] mfs = this.copyMappedFiles(0);


    if (null == mfs)
        return 0;

    int mfsLength = mfs.length - 1;
    int deleteCount = 0;
    //
    List<MappedFile> files = new ArrayList<MappedFile>();
    if (null != mfs) {
        // 迭代遍历
        for (int i = 0; i < mfsLength; i++) {
            //
            MappedFile mappedFile = (MappedFile) mfs[i];
            // 文件最大存活截止时间点
            long liveMaxTimestamp = mappedFile.getLastModifiedTimestamp() + expiredTime;
            // cleanImmediately 上层会判断磁盘占用率达到95%,会设置为true
            //
            if (System.currentTimeMillis() >= liveMaxTimestamp || cleanImmediately) {
                // 删除
                if (mappedFile.destroy(intervalForcibly)) {
                    // 删除的文件
                    files.add(mappedFile);
                    // 删除数量
                    deleteCount++;

                    // 达到最大删除数 10
                    if (files.size() >= DELETE_FILES_BATCH_MAX) {
                        break;
                    }

                    if (deleteFilesInterval > 0 && (i + 1) < mfsLength) {
                        try {
                            // 删除需要点时间
                            Thread.sleep(deleteFilesInterval);
                        } catch (InterruptedException e) {
                        }
                    }
                } else {
                    break;
                }
            } else {
                //avoid deleting files in the middle
                break;
            }
        }
    }

    deleteExpiredFile(files);

    return deleteCount;
}

删除有两种方式

  1. 根据偏移量删除,例如ConsumeQueue的删除策略,ConsumeQueue中每条消息的长度固定是20字节,可以根据逻辑上是第几条消息进行删除操作。

  1. 根据过期时间清除,如RocketMq中存存消息的日志文件CommitLog文件,其根据文件的最后修改时间来确定是否删除文件。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值