RocketMQ消息存储

RocketMQ主要存储文件包含消息文件(commitLog)、消息消费队列文件(consume-queue)、Hash索引文件(IndexFile)、检测点文件(checkpoint)、abort(关闭异常文件)、单个消息存储文件、消息消费队列文件、Hash索引文件长度固定以便使用内存映射机制进行文件的读写操作。RocketMQ组织文件以文件的起始偏移量来命名文件,这样根据偏移量能快速的定位到真实的物理文件。RocketMQ基于内存映射文件机制提供了同步刷盘与异步刷盘两种机制,异步刷盘是指在消息存储时先追加到内存映射文件,然后启动专门的刷盘线程定时将内存中的数据刷盘到磁盘。

commitLog,消息存储文件,RocketMQ为了保证消息发送的高吞吐量,采用单一文件存储所有主题的消息,保证消息存储是完全的顺序写,但这也给文件读取带来了不便,RocketMQ为了方便消息消费构建了消息消费队列文件,基于主题与队列进行组织,同时RocketMQ为消息实现了Hash索引,可以为消息设置索引键,根据索引能够快速从commitLog文件中检索出消息。

当消息到达CommitLog文件后,会通过RequestMessageService线程接近实时的将消息转发给消息消费队列文件好索引文件,为了安全起见,RocketMQ引入了abort文件,记录Broker的停机是正常关闭还是异常关闭,在重启Broker时为了保证CommitLog文件,消息消费队列文件与Hash索引文件的正确性,分别采取不同的策略来恢复文件。

RocketMQ不会永久存储消息文件、消息消费队列文件,而是启用文件过期机制,并在磁盘空间不足或默认在凌晨四点删除过期文件,文件默认保存72小时并且在删除文件时并不会判断该消息文件上的消息是否被消费

一、消息发送存储流程:

RocketMQ将所有主题的消息存储在同一个文件中,确保消息发送时顺序写文件,尽最大的能力确保消息发送的高性能与高吞吐量。因为基于消费主题进行订阅消费,所以为了提高消费效率,引入了消息消费队列文件,每个消息主题包含多个消息消费队列,每一个消息队列有一个消息文件。IndexFile索引文件为了加速消息的检索性能,根据消息的属性快速的从CommitLog中检索出来,

  • CommitLog:消息存储文件,所有消费主题的消息都存储在CommitLog文件中。
  • ConsumeQueue:消息消费队列,消息到达CommitLog文件后,将异步转发到消息消费队列,供消息消费者消费。
  • IndexFile:消息索引文件,主要存储消息Key 与 Offset的对应关系。
  • 事务状态服务:存储每条消息的事务状态。
  • 定时消息服务:每一个延迟级别对应一个消息消费队列,存储延迟队列的消息拉取进度。

1、如果当前Broker停止工作或者Broker为Slave角色或当前Rocket不支持写入则拒绝消息写入,如果消息主题长度超过256个字符,消息属性长度超过65536个字符将拒绝该消息写入。

2、如果消息的延迟级别大于0,将消息的原主题名称与原消息队列ID存入消息属性中,用延迟消费主题SCHEDULE_TOPIC

、消息队列ID更新原先消息的主题与队列,这是并发消息消费重试关键的一步。

3、获取当前可以写入的CommitLog文件,每一个文件默认 1 G,一个文件写满后再创建另一个,MappedFileQueue可以看做是***/store/commitLog文件夹,而MappedFile则对应该文件下一个个的文件。

RocketMQ消息存储_第1张图片

4、在写入CommitLog之前,先申请pushMessageLock,这说明将消息写入到CommitLog文件中是串行的。

5、设置消息的存储时间,如果马佩佩的File为空,表明上图中的目录下不存在任何文件,说明消息是第一次发送,用偏移量0创建第一个Commit文件,如上图,如果创建失败,抛出CREATE_MAPPEDFILE_FAILED,很有可能是磁盘空间不足或者权限不够。

6、将消息追加到MappedFile中,首先先获取MappedFile当前写指针,如果currentPos大于或者等于文件大小则表明文件已写满,抛出异常,如果currentPos小于文件大小,通过slice()方法创建一个与MappedFile的共享内存区并设置position为当前指针。

7、创建全局唯一消息ID,消息ID有16字节,消息ID。

8、获取该消息在消息队列的偏移量。CommitLog中保存了当前所有消息队列的当前待写入偏移量。

9、根据消息体的长度,主题的长度,属性的长度,结合消息存储格式计算消息的总长度。

10、如果消息长度+最小文件剩余长度 大于>commitLog文件的剩余空间返回响应状态,然后Broker重新建一个新的CommitLog文件。

11、将消息内容存储到ByteBuffer中,然后创建AppendMessageResult。这里只是将消息存储在MappedFile对应的内存映射Buffer上,并没有刷写到磁盘。

12、更新消息队列逻辑偏移量。

13、处理完消息追加逻辑后将释放putMessageLock锁。

14、DefaultAppendMessageCallBack#doAppend只是将消息追加在内存中,需要根据是同步刷盘还是异步刷盘方式,将内存中的数据持久化到磁盘。

二、存储文件组织与内存映射:

RocketMQ通过使用内存映射文件来提高IO访问性能,无论是commitLog、consumerQueue、还是IndexFile,单个文件都被设计为固定长度,如果一个文件写满以后在创建一个新文件,文件名就为该文件第一条消息对应的全局物理偏移量。

1、MappedFileQueue映射文件队列:

MappedFileQueue是MappedFile的管理容器,MappedFileQueue是对应存储目录的封装。

public class MappedFileQueue {
   
    private static final int DELETE_FILES_BATCH_MAX = 10;
    //存储目录
    private final String storePath;
    //单个文件的存储大小
    private final int mappedFileSize;
    //MappedFile 文件集合
    private final CopyOnWriteArrayList mappedFiles = new CopyOnWriteArrayList();
    //创建MappedFile文件服务类
    private final AllocateMappedFileService allocateMappedFileService;
    //当前刷盘指针,表示该指针之前的所有数据全部持久化到磁盘
    private long flushedWhere = 0;
    //当前提交数据指针,内存中ByteBuffer当前的指针,该值大于等于flushedWhere。
    private long committedWhere = 0;

    private volatile long storeTimestamp = 0;



}

2、MappedFile内存映射文件:

public class MappedFile extends ReferenceResource {
    //操作系统每页大小 默认 4K
    public static final int OS_PAGE_SIZE = 1024 * 4;
    protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
    //当前JVM实例中的MappedFile的虚拟内存
    private static final AtomicLong TOTAL_MAPPED_VIRTUAL_MEMORY = new AtomicLong(0);
    //当前JVM实例中MappedFile对象个数。
    private static final AtomicInteger TOTAL_MAPPED_FILES = new AtomicInteger(0);
    //当前该文件的写指针[从0开始内存映射文件的写指针]  
    protected final AtomicInteger wrotePosition = new AtomicInteger(0);
    //ADD BY ChenYang
    //当前文件的提交指针,如果开启【transitStorePoolEnable】则数据会存储在
    //TransitionStorePool中,然后提交到内存映射ByteBuffer中,在刷写到磁盘。
    protected final AtomicInteger committedPosition = new AtomicInteger(0);
    //刷写到磁盘指针,该指针之前的数据持久化到磁盘中
    private final AtomicInteger flushedPosition = new AtomicInteger(0);
    //文件大小
    protected int fileSize;
    //文件通道
    protected FileChannel fileChannel;
    /**
     * Message will put to here first, and then reput to FileChannel if writeBuffer is not null.
     */
    //堆内存ByteBuffer如果不为空数据首先存储在该Buffer中,然后提交到MappedFile
    //对应的内存映射文件Buffer,transitStorePoolEnable为true时不为空。
    protected ByteBuffer writeBuffer = null;
    //堆内存池
    protected TransientStorePool transientStorePool = null;
    //文件名称
    private String fileName;
    //该文件的初始偏移量
    private long fileFromOffset;
    //物理文件
    private File file;
    //物理文件对应的内存映射Buffer
    private MappedByteBuffer mappedByteBuffer;
    //文件最后一次内容写入时间
    private volatile long storeTimestamp = 0;
    //是否是MappedFileQueue队列中第一个文件
    private boolean firstCreateInQueue = false;

}

三、存储文件:

1、CommitLog文件:

RocketMQ消息存储_第2张图片

RocketMQ存储文件夹:

  1. CommitLog:消息存储目录;
  2. Config:运行期间一些配置信息【consumerFilter.json-主题消息过滤信息;consumerOffset.json-集群消费模式消息消费进度;delayOffSet.json-延时消息队列拉取进度;subscriptionGroup.json-消息消费组配置信息;topics.json-topic配置属性】。
  3. consumerqueue:消息消费队列存储目录;
  4. index:消息索引文件存储目录;
  5. abort:如果存在abort文件说明Broker非正常关闭,该文件默认启动时创建,正常退出之前删除。
  6. checkPoint:文件检测点,存储CommitLog文件最后一次刷盘时间戳、consumeQueue最后一次刷盘时间、index索引文件最后一次刷盘时间戳。

 

2、ConsumeQueue文件:

RocketMQ消息存储_第3张图片

第一级目录为消息主题,第二级目录为主题的消息队列。

3、Index索引文件

4、checkPoint文件。

四、消息队列与索引文件恢复:

由于RocketMQ存储首先将消息全量存储在CommitLog文件中,然后异步生成转发任务更新ConsumeQueue、Index文件。如果消息成功存储到CommitLog文件中,转发任务未成功执行,此时消息服务器基于某个原因宕机,导致CommitLog、ConsumeQueue、IndexFile文件不一致。

1、Broker正常停止文件恢复:

2、Broker异常停止文件恢复。

五、文件刷盘机制:

1、Broker同步刷盘:

指的是消息追加到内存映射文件的内存中后,立即将数据从内存刷写到磁盘文件,由CommitLog的handDiskFlush方法实现。

2、Broker异步刷盘:

RocketMQ消息存储_第4张图片

五、过期文件删除机制:

RocketMQ消息存储_第5张图片

1、三个配置属性的含义:

RocketMQ消息存储_第6张图片

 2、继续执行删除文件操作:

RocketMQ消息存储_第7张图片

 

你可能感兴趣的:(【微服务】)