高鑫 阿里云智能GTS-SRE团队 高级技术支持工程师

多年云计算、金融行业应用运维工作经验,银行核心应用运维架构设计与应用维护。专注中间件及数据库技术,现就职于阿里云智能GTS-SRE团队,主要负责中间件领域客户技术支持工作。

引言

RocketMQ是阿里开源的分布式消息中间件,跟其它中间件相比,RocketMQ的特点是纯JAVA实现、集群和HA实现相对简单、在发生宕机和其它故障时消息丢失率更低,具有良好的高可用架构及稳定性。其发展的迭代历史如下图所示,从2007年至今已发展超过10年。

图1:RocketMQ迭代历史

消息存储架构

图2:RocketMQ消息存储架构

RocketMQ的消息存储架构如上图所示,可以看到主要由三个跟消息存储相关的文件构成。

  • CommitLog:消息及元数据的存储主体。消息内容不是定长的,同时单个文件大小默认1G,文件名长度为20位,左边补零,剩余为起始偏移量。比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当第一个文件满了,再写入下一个文件。
  • ConsumeQueue:消息消费队列。RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要根据topic在commitlog文件中进行检索消息,效率将会非常低效。ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset、消息大小size和消息Tag的HashCode值。所以ConsumeQueue文件可以看成是基于topic的CommitLog索引文件。ConsumeQueue文件夹的组织方式如下:topic/queue/file三层组织结构。而ConsumeQueue存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。与Commitlog一样,ConsumeQueue文件采取了定长设计,单个文件由30W个条目组成,每一个条目共20个字节,分别为8字节的CommitLog物理偏移量、4字节的消息长度、8字节tag hashcode,Comsumer可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.72M。
  • IndexFile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。Index文件的存储位置是:$HOME /store/index${fileName},文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存2000W个索引。

页缓存与内存映射

页缓存(PageCache)是OS对文件的缓存,用于加速对文件的读写。一般来说,程序对文件进行顺序读写的速度几乎接近于内存的读写速度,主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化,将一部分的内存用作PageCache。对于数据的写入,OS会先写入至Cache内,随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上。对于数据的读取,如果一次读取文件时出现未命中PageCache的情况,OS从物理磁盘上访问读取文件的同时,会顺序对其他相邻块的数据文件进行预读取。

在RocketMQ中,ConsumeQueue逻辑消费队列存储的数据较少,并且是顺序读取,在page cache机制的预读取作用下,Consume Queue文件的读性能几乎接近读内存,即使在有消息堆积情况下也不会影响性能。而对于CommitLog消息存储的日志数据文件来说,读取消息内容时会产生较多的随机访问读取,严重影响性能。如果选择合适的系统IO调度算法,比如设置调度算法为“Deadline”(此时块存储采用SSD的话),随机读的性能也会有所提升。

另外,RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(正因为需要使用内存映射机制,故RocketMQ的文件存储都使用定长结构来存储,方便一次将整个文件映射至内存)。

消息刷盘

图3:消息刷盘机制
  • 同步刷盘:如上图所示,当消息真正持久化至磁盘后,RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般适用于金融业务应用。
  • 异步刷盘:能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。

结语

以上便是对RocketMQ消息存储技术的相关介绍,希望对读者有所帮助。