RocketMQ源码:broker存储文件组织和内存映射

*** 返回队列中第一个MappedFile,这里忽略索引越界异常,可能一个都没有,返回null* 先判断mappedFiles是否为空,然后get(0),因为存在并发,所以需要即使判断为空,还是可能索引越界* @return*/if (!try {//ignore复制代码while (!try {//由于get和size没有加锁// size获取的值可能是旧的,所以可能出现错误的大小,导致索引越界// get获取的值可能是旧的数组,所以可能出现索引越界break;
摘要由CSDN通过智能技术生成

1. 存储设计

RocketMQ存储文件主要包括:CommitLog文件、ConsumerQueue文件、Index文件

  • CommitLog文件:所有Topic的消息按照抵达顺序依次追加到CommitLog中,一旦写入不支持修改
  • ConsumeQueue文件:消息消费队列,用于消费者消费,即消费者通过此文件来从CommitLog中获取消息。消息达到CommitLog后,将异步转发到ConsumeQueue文件
  • Index文件:消息索引,主要存储消息key与offset的对应关系

RocketMQ将所有Topic的消息都存储在同一个CommitLog文件中,一般按照Topic来检索消息,所以为了提高消息消费的效率,RocketMQ引入了ConsumeQueue文件(消费队列),每一个Topic包含多个消息消费队列,每一个消费队列都有一个文件。

为了根据消息的属性从CommitLog文件中快速检索消息,RocketMQ引入了Index索引文件。

存储目录为:

1.1 基本原理

  • Page Cache:Page Cache是文件系统层的Cache,主要用来减少对磁盘的I/O操作,通过对磁盘的访问变为物理内存的访问,缓存的是内存页面,操作时按照页为基本单位。在Linux系统中写入数据的时候并不会直接写到硬盘上,而是会先写到Page Cache中,并打上dirty标识,由内核线程flusher定期将被打上dirty的页发送给IO调度层,最后由IO调度决定何时落地到磁盘中,而Linux一般会把还没有使用的内存全拿来给Page Cache使用。而读的过程也是类似,会先到Page Cache中寻找是否有数据,有的话直接返回,如果没有才会到磁盘中去读取并写入Page Cache,然后再次读取Page Cache并返回。而且读的这个过程中操作系统也会有一个预读的操作,你的每一次读取操作系统都会帮你预读出后面一部分数据。当你一直在使用预读数据的时候,系统会帮你预读出更多的数据(最大到128K)。

  • Buffer Cache:Buffer Cache是针对设备的,实际操作按块为基本单位,对于裸盘的读写会占用Buffer Cache,当读写完成之后,会归还给操作系统。

  • 在linux2.4内核中Buffer CachePage Cache是共存的,因为文件的读写最终会转化为块设备的读写,即同一份文件的数据,可能既在Buffer Cache中也在Page Cache中,这样就造成了物理内存的浪费。

  • linux2.6内核对Buffer CachePage Cache进行了合并,统一为Page Cache。当进行文件读写时,如果文件在磁盘上的存储块是连续的,那么文件在Page Cache中对应的页是普通的page,如果文件在磁盘上的数据块是不连续的,或者是设备文件,那么文件在Page Cache中对应的页就是Buffer Cache

  • 查看内存情况

    $ # cat /proc/meminfo                     
    MemTotal:        3876772 kB
    MemFree:          126704 kB
    MemAvailable:     137132 kB
    Buffers:              48 kB
    Cached:           258648 kB
    SwapCached:        12344 kB
    ...省略...
    
    Buffers: 表示`Buffer Cache`的容量
    Cached: 表示位于物理内存中的页缓存`Page Cache`
    SwapCached:表示位于磁盘交换区的页缓存`Page Cache`
    实际的`Page Cache`容量=Cached+SwapCached
    复制代码
  • linux底层提供mmap将文件映射进虚拟内存,对文件的读写变成对内存的读写,能充分利用Page Cache,但是如果对文件进行随机读写,会使虚拟内存产生很多缺页(Page Fault)中断,此时操作系统需要将磁盘文件的数据再次加载到Page Cache,这个过程比较慢。如果对文件进行顺序读写,读和写的区域都是被操作系统缓存过的热点区域,不会产生大量的缺页中断,文件的读写操作相当于直接内存的操作,性能会提升很多。如果内存不够充足,内核把内存分配给Page Cache后,空闲内存会变少,如果程序有新的内存分配或者缺页中断,但是空闲内存不够,内核需要花费时间将热度低的Page Cache内存回收掉,此时性能会下降。当遇到操作系统进行脏页回写,内存回收,内存换入换出等情形时,会产生较大的读写延迟,造成存储引擎偶发的高延迟,针对这种现象,RocketMQ采用了多种优化技术,比如内存预分配,文件预热,mlock系统调用,读写分离等,来保证利用Page Cache优点的同时,消除其带来的延迟。

  • 工具查看:hcache是基于pcstat,pcstat可以查看文件是否被缓存和根据pid来查看缓存了哪些文件,hcachepcstat的增强版本,增加了查看整个系统Cache和根据Cache大小排序的功能:

    查看使用Cache最多的3个进程
    $ hcache --top 3  
    +----------------------------------+--------------+-------+--------+---------+
    | Name                             | Size (bytes) | Pages | Cached | Percent |
    |----------------------------------+--------------+-------+--------+---------|
    | /usr/share/atom/atom             | 81137776     | 19810 | 19785  | 099.874 |
    | /usr/bin/dockerd                 | 68608880     | 16751 | 14321  | 085.493 |
    | /usr/share/atom/snapshot_blob.bin| 54619240     | 13335 | 13335  | 100.000 |
    +----------------------------------+--------------+-------+--------+---------+
    复制代码

1.2 CommitLog

  • RocketMQ Broker单个实例下所有的Topic都使用同一个日志数据文件(CommitLog)来存储(即单个实例消息整体有序),这点与kafka不同(kafka采用每个分区一个日志文件存储)

  • CommitLog单个文件大小默认1G,文件文件名是起始偏移量,总共20位,左边补零,起始偏移量是0。假设文件按照默认大小1G来算

    • 第一个文件的文件名为00000000000000000000 ,当第一个文件被写满之后,开始写入第二个文件
    • 第二个文件的文件名为00000000001073741824 ,1G=1073741824=1024*1024*1024
    • 第三个文件的文件名是00000000002147483648,(文件名相差1G=1073741824=1024*1024*1024)
  • CommitLog按照上述命名的好处是给出任意一个消息的物理偏移量,可以通过二分法进行查找,快速定位这个文件的位置,然后用消息物理偏移量减去所在文件的名称,得到的差值就是在该文件中的绝对地址

1.3 ConsumeQueue

ConsumeQueue是消息消费队列,它是一个逻辑队列,相当于CommitLog的索引文件。因为RocketMQ的队列不存储任何实际数据,它只存储CommitLog中的【起始物理位置偏移量,消息的内容大小,消息Tag的哈希值】,每一个ConsumeQueue存储的格式如下,总共20B。存tag是为了在消费者取到消息offset后先根据tag做一次过滤,剩下的才需要到CommitLog中取消息详情:

每个ConsumeQueue都有一个queueId,queueId 的值为0到TopicConfig配置的队列数量。比如某个Topic的消费队列数量为4,那么四个ConsumeQueuequeueId就分别为0、1、2、3。

消费者消费时会先从ConsumeQueue中查找消息在CommitLog中的offset,再去CommitLog中找原始消息数据。如果某个消息只在CommitLog中有数据,没在ConsumeQueue中, 则消费者无法消费

ConsumeQueue类对应的是每个topic和queuId下面的所有文件。默认存储路径是$HOME/store/consumequeue/{topic}/{queueId}/{fileName},每个文件由30w条数据组成,单个文件的大小是30w x 20Byte,即每个文件为600w字节,单个消费队列的文件大小约为5.722M=(600w/(1024*1024))

1.4 Index文件

IndexFile:索引文件,物理存储上,文件名为创建时间的时间戳命名,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存2000W个索引

IndexFile(索引文件)由IndexHeader(索引文件头), Slot(槽位)和Index(消息的索引内容)三部分构成。对于每个IndexFile来说IndexHeader是固定大小的,Slot是索引的目录,用于定位IndexIndexFile中存储的物理位置。存储图:

1.5 checkpoint文件

checkpoint检查点文件的作用是记录CommitLog、ConsumeQueue、Index文件的刷盘时间点,文件固定长度为4kb,只用该文件的前24个字节

  • physicMsgTimestamp:CommitLog文件刷盘时间点
  • logicsMsgTimestamp:ConsumeQueue文件刷盘时间点
  • indexMsgTimestamp:Index文件刷盘时间点

1.6 TransientStorePool机制

RocketMQ为了降低PageCache的使用压力,引入了transientStorePoolEnable机制,即内存级别的读写分离机制

默认情况,RocketMQ将消息写入PageCache,消费时从PageCache中读取消息。但是这样在高并发下PageCache压力会比较大,容易出现瞬时broker busy异常。RocketMQ通过开启transientStorePoolEnable=true,将消息写入堆外内存并立即返回,然后异步将堆外内存中的数据批量提交到PageCache,再异步刷盘到磁盘中。这样的好处就是形成内存级别的读写分离,发送写入消息是向堆外内存,消费读取消息是从PageCache

该机制的缺点就是如果意外导致broker进程异常退出,已经放入到PageCache中的数据不会丢失,而存储在堆外内存的数据会丢失

2. MappedFileQueue

RocketMQ使用MappedFile、MappedFileQueue来封装存储文件。MappedFileQueueMappedFile的管理容器,使用CopyOnWriteArrayList来管理所有的MappedFileMappedFileQueue提供查找目录下Map

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值