RocketMQ MappedFile 存储原理 文件预热原理

本文详细解析RocketMQ中MappedFile类的存储机制,包括文件映射管理、内存页预热及文件创建策略等内容,旨在帮助理解RocketMQ的高效存储设计。

RocketMQ底层存储文件有commitLog,consumerqueue,indexfile,checkpoint文件。其中使用到JDK新的IO方式NIO包对磁盘文件进行操作。MappedFile类就是使用NIO API对磁盘文件操作的包装类,mq使用这个类对文件进行写入、读取、刷盘等操作。在编程方式上对磁盘文件抽象出统一操作的工具类是很好的设计,提高开发效率。所以每一个log或queue,indexfile文件被创建时相同的都会在内存创建一个对应的文件内存映射管理对象mapfile。

学习mapfile是如何对磁盘文件进行管理的,是阅读mq对commitlog、queue文件高效存储设计机制的核心基础。mapfile类使用了nio api进行读写,所以需要一定的nio bytebuf类的操作基础知识。

MappedFile的类变量以及实例变量的作用

mq需要知道占用了多少内存以及当前有多少个映射文件,方便管理。所以MappedFile定义了以下类变量记录数据

// 操作系统数据页 4K,unix系列通常是这个大小
public static final int OS_PAGE_SIZE = 1024 * 4;
// mq总共分配的映射文件内存大小
private static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0);
// mq总共创建的内存文件映射数量
private static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0);

然后是每个mapfile所代表的映射文件的实例变量。mapfile分为同步刷盘和异步刷盘两种机制,为了实现异步刷盘特地开了3个指针,这样才能搞清楚:未提交数据、已提交未刷盘数据、已刷盘数据。然后由2个异步线程定时对数据进行提交和刷盘。

// 当前数据的写入位置指针,下次写数据从此开始写入
protected final AtomicInteger wrotePosition = new AtomicInteger(0);
// 当前数据的提交指针,指针之前的数据已提交到fileChannel,commitPos~writePos之间的数据是还未提交到fileChannel的
protected final AtomicInteger committedPosition = new AtomicInteger(0);
// 当前数据的刷盘指针,指针之前的数据已落盘,commitPos~flushedPos之间的数据是还未落盘的
private final AtomicInteger flushedPosition = new AtomicInteger(0);
// 文件大小,单位字节
protected int fileSize;
// 磁盘文件的内存文件通道对象
protected FileChannel fileChannel;
// 异步刷盘时数据先写入writeBuf,由CommitRealTime线程定时200ms提交到fileChannel内存,再由FlushRealTime线程定时500ms刷fileChannel落盘
protected ByteBuffer writeBuffer = null;
// 堆外内存池,服务于异步刷盘机制,为了减少内存申请和销毁的时间,提前向OS申请并锁定一块对外内存池,writeBuf就从这里获取
protected TransientStorePool transientStorePool = null;
// 文件名,通常会以此文件开始写入的字节命名
private String fileName;
// 文件起始的写入字节数,初始化时是Long.parseLong(fileName)
private long fileFromOffset;
// 磁盘文件对象
private File file;
// 磁盘文件的内存映射对象,同步刷盘时直接将数据写入到mapedBuf
private MappedByteBuffer mappedByteBuffer;
// 此文件最近一次追加数据的时间戳
private volatile long storeTimestamp = 0;

MappedFile的初始化方法

public void init(final String fileName, final int fileSize,
    final TransientStorePool transientStorePool) throws IOException {
    init(fileName, fileSize);
    // 如果选择了带内存池的构造方法则代表要异步刷盘
    this.writeBuffer = transientStorePool.borrowBuffer();
    this.transientStorePool = transientStorePool;
}
// 默认的构造方法
private void init(final String fileName, final int fileSize) throws IOException {
    this.fileName = fileName;
    this.fileSize = fileSize;
    this.file = new File(fileName);
    // fileFromOffset是代表文件的起始字节数
    this.fileFromOffset = Long.parseLong(this.file.getName());
    boolean ok = false;
    // 检查文件所在的目录
    ensureDirOK(this.file.getParent());
    try {
        // NIO API创建文件通道对象对文件操作
        this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
        // mmap函数,对写入数据和读取数据实现OS到用户态的零拷贝
        this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
        // 全局记录用了多少文件映射内存
        TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);
        // 全局记录开了多少文件通道对象
        TOTAL_MAPPED_FILES.incrementAndGet();
        ok = true;
    } catch (FileNotFoundException e) {
        log.error("Failed to create file " + this.fileName, e);
        throw e;
    } catch (IOException e) {
        log.error("Failed to map file " + this.fileName, e);
        throw e;
    } finally {
        if (!ok && this.fileChannel != null) {
            this.fileChannel.close();
        }
    }
}

MappedFile如何写入数据的

mapfile往文件追加数据的逻辑是在appendMessage函数,根据消息可以追加单条或者批量消息。所有文件数据写入入口都是下面的函数,CommitLog、ConsumerQueue,那么不同文件由于磁盘的文件数据组织方式不一样如何实现定制化的序列化存储?依靠AppendMessageCallback实现,不同的文件写入数据传入各自实现,在统筹逻辑中回调方法。

// 单条消息
public AppendMessageResult appendMessage(final MessageExtBrokerInner msg, final AppendMessageCallback cb) {
    return appendMessagesInner(msg, cb);
}
// MessageExtBatch批量消息
public A
### RocketMQ 持久化机制原理存储实现 #### 1. 概述 RocketMQ 是一种高性能、高可靠的消息中间件,其持久化机制是保障消息可靠性的重要组成部分。通过合理设计的存储结构和优化策略,RocketMQ 能够在保证性能的同时支持海量消息的高效处理[^1]。 #### 2. 存储架构 RocketMQ存储系统由两部分组成:CommitLog 和 ConsumeQueue。 - **CommitLog**: 它是一个物理日志文件,用于按时间顺序记录所有的消息内容。每条消息按照固定的偏移量存储,这种顺序写的方式能够充分利用磁盘的特性,提升写入性能[^3]。 - **ConsumeQueue**: 它是一种逻辑队列,主要用于加速消费者端的消息检索过程。ConsumeQueue 中只保存消息的关键索引信息(如主题名、队列ID、消息大小等),而不包含具体的消息体。这种方式减少了内存占用并提高了查询效率[^4]。 #### 3. 持久化流程 当生产者向 Broker 发送一条消息时,Broker 需要完成以下几个步骤来确保消息被成功持久化: 1. 将消息追加到 CommitLog 文件中; 2. 更新对应的 ConsumeQueue 数据; 3. 如果启用了同步刷盘模式,则等待数据完全写入磁盘后再返回确认给生产者;如果是异步刷盘,则立即响应客户端请求[^2]。 #### 4. 刷盘方式 为了平衡性能与可靠性之间的关系,RocketMQ 支持两种不同的刷盘策略: - **同步刷盘**:每次接收到新消息后立即将其刷新至硬盘,虽然安全性较高但会影响吞吐量; - **异步刷盘**:允许操作系统缓存一定数量的数据后再批量写入磁盘,从而显著提高写操作的速度,但在极端情况下可能会丢失最近未落地的日志。 #### 5. “零拷贝”技术的应用 RocketMQ 使用了 MMPP (Memory-Mapped File & Page Cache) 技术实现了所谓的“零拷贝”,即无需显式地将数据从内核空间复制到用户空间即可完成传输。这一方法不仅降低了 CPU 开销还加快了网络发送速率,在实际应用中有助于进一步增强系统的整体表现。 ```python # 示例代码展示如何配置刷盘方式 brokerConfig = { 'flushDiskType': 'ASYNC_FLUSH' # 或 SYNC_FLUSH 表示启用同步刷盘 } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值