RocketMQ消息存储之物理存储与逻辑存储

目录

一、几种常用的消息存储类型比较

二、RocketMQ存储文件的组成部分

三、物理存储之CommitLog

1、CommitLog目录结构

2、CommitLog的存储结构 

四、逻辑存储之ConsumeQueue和Index

1、ConsumeQueue

2、Index

1)IndexHead 数据 

2)Hash槽

3)Index 条目列表

五、总结


        RocketMQ消息存储主要发生在Broker端,作为一种高可用消息中间件,RocketMQ具有独特的消息存储方式:RocketMQ消息存储分为物理存储和逻辑存储,物理存储发生在CommitLog文件中;逻辑存储发生在ConsumeQueue和Index文件中。

一、几种常用的消息存储类型比较

        下表对比下业界常用的存储类型:

存储类型

常见的几款MQ产品

存储方式

存储效率排名

易于实现与快速集成排名

文件系统

RocketMQ、Kafka、RabbitMQ

消息刷盘(同步刷盘、异步刷盘)

1

3

关系型数据库DB

ActiveMQ

默认采用KahaDB做消息存储,可选JDBC方式做消息持久化

3

1

分布式KV存储

ZeroMQ

依赖levelDB、RocksDB和Redis等作为消息持久化存储

2

2

        从上述表格可以看出从存储效率方面来看,文件系统存储类型胜出,而文件系统通常有异步和同步刷盘两种方式。对于消息中间件来说,本身应该尽量减少对于外部第三方中间件的依赖,因为相对于应用来说,消息中间件本身就是一种依赖,如果消息中间件又有第三方依赖,可能会导致设计过于复杂,因此采用文件系统在这方面还是有比较大的优势的。

二、RocketMQ存储文件的组成部分

        RocketMQ文件存储在store文件夹里,里面包含了commitlog、config、consumequeue、index这4个文件夹和abort、checkpoint两个文件。store文件夹下的具体目录参考下面目录树:

RocketMQ
      |--store
             |-commitlog
             |     |-00000000000000000000
             |     |-00000000001073741824
             |-config
             |     |-consumerFilter.json
             |     |-consumerOffset.json
             |     |-delayOffset.json
             |     |-subscriptionGroup.json
             |     |-topics.json
             |-consumequeue
             |     |-SCHEDULE_TOPIX_XXX
             |     |-topicA
             |     |-topicB
             |            |-0
             |            |-1
             |            |-2
             |            |-3
             |                  |-00000000000000000000
             |                  |-00000000001073741824
             |-index
             |     |-00000000000000000000
             |     |-00000000001073741824
             |-abort
             |-checkpoint

        下文主要介绍 CommitLog、ConsumeQueue和Index。

三、物理存储之CommitLog

        在RocketMQ中CommitLog的作用是存储所有topic的消息,CommitLog的存储文件地址是$HOME\store\commitlog\${fileName},每个文件默认大小是1GB,当超过1GB时,会自动创建一个新的CommitLog文件进行存储。

1、CommitLog的目录结构

        CommitLog的文件名长度为20位,左边补齐零,最右边为偏移量。例如:文件名00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;假设第一个文件存满了,则会生成第二个文件00000000001073741824,起始偏移量为1073741824,依次类推。

图1  CommitLog的目录结构

        CommitLog每条消息存储长度不同,其逻辑视图可以参考下图,每条消息的前4个字节存储该消息的总长度。

图2 CommitLog的逻辑视图

2、CommitLog的存储结构 

        CommitLog的存储结构如下:

图3 CommitLog的存储结构

编号

字段简称

字段长度(字节)

含义

1

msgLen

4

消息的长度,消息的长度是整个消息体所占用的字节数的大小

2

magicCode

4

魔数,是固定值,MAGICCODE = 0xdaa320a7

3

bodyCRC

4

消息体的CRC,用于防止网络、硬件等故障导致数据与发送时不一致带来的问题,当broker重启recover时会校验

4

queueId

4

队列id,表示消息发到了哪个consumequeue

5

flag

4

网络通信层标记。创建Message对象时由生产者通过构造器设定的flag值,可以用于标记是请求,还是响应亦或是oneway(producer发只管发出消息不管返回值)类型,在RemotingCommand有用到

6

queueOffset

8

在consumequeue中的偏移量

7

physicalPosition

8

代表消息在commitLog中的物理起始地址偏移量

8

sysFlag

4

指明消息是事务状态等消息特征,可参考MessageSysFlag类。二进制为四个字节从右往左数:

  1. 当第1个字节为1(值为1)时表示表示消息是压缩的(Compressed);

  2. 当第2个字节为1(值为2)表示多消息(MultiTags);

  3. 当4个字节均为0(值为0)时表示非事务消息;

  4. 当第3个字节为1(值为4)时表示prepared消息;

  5. 当第4个字节为1(值为8)时表示commit消息;

  6. 当第3/4个字节均为1时(值为12)时表示rollback消息。

9

msg born timestamp

8

producer发送消息的时间戳

10

msg host

8

producer的host(address:port)

11

store timestamp

8

消息存储的时间戳

12

store host

8

broker的host(address:port)

13

reconsume time

4

消息被某个订阅组重新消费了几次(订阅组之间独立计数),因为重试消息发送到了topic名字为%retry%groupName的队列queueId=0的队列中去了,成功消费一次记录为0;

14

prepare transaction offset

8

表示是prepared状态的事务消息偏移量,RocketMQ事务消息基于两阶段提交

15

body length

4

消息体长度

16

msg body

bodyLength

消息体的内容

17

topic length

1

topic的长度,topc的长度最多不能超过127个字节,超过的话存储会出错(有前置校验)

18

topic

topicLength

topic的内容值

19

properties Length

2

属性值大小

20

properties

propertiesLength

RocketMQ内部用到的一些属性。例如发送消息的TAG就存放在Properties里面,Properties中的一些常用key都定义在了MessageConst里面

四、逻辑存储之ConsumeQueue和Index

        RocketMQ的消息存储采用的是混合型的存储结构,所谓混合型存储结构,就是Broker单个实例下所有的队列共用一个CommitLog来存储,这样会导致查询的时候无法快速定位具体的某个消息。于是就有了ConsumeQueue和Index两个逻辑存储文件。

        RocketMQ的消息存储架构可以参考下图。

图4 RocketMQ的消息存储架构

        从上图可以看出,Producer生产任意的消息后,无论Topic是否相同,都会将消息内容存储到CommitLog中,当CommitLog存储满了,则会创建一个新的CommitLog文件继续存储新生成的消息。此外,在Broker端,会有一个后台服务线程——ReputMessageService会不停地分发请求并异步构建ConsumeQueue(逻辑消费队列)和IndexFile(索引文件)。而Consumer端可以根据ConsumeQueue中的数据查找具体的消息。IndexFile则为消息查询提供了一种通过key或时间区间来查询消息的方法。下面主要介绍下ConsumeQueue和IndexFile。 

1、ConsumeQueue

        RocketMQ基于主题订阅模式实现消息的消费,但是在CommitLog中存储的消息是不连续的,如果从CommitLog检索该消息文件会很慢,为了提高效率,对应的主题的队列建立了索引文件,为了加快消息的检索和节省磁盘空间,每一个ConsumeQueue条目存储了消息的关键信息CommitLog文件中的偏移量、消息长度、tag的hashcode值。下图展示消息到ConsumeQueue的原理。

图5 消息到ConsumeQueue及从ConsumeQueue消费的示意图

        一个ConsumeQueue表示一个topic的一个queue,类似于kafka的一个partition,单个ConsumeQueue文件中默认包含30万个条目,ConsumeQueue文件名采取定长设计,每一个条目共20个字节,分别为8字节的CommitLog物理偏移量、4字节的消息长度、8字节tag hashcode,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个ConsumeQueue文件大小约5.72M。

        ConsumeQueue可以看成是基于topic的CommitLog索引文件,故ConsumeQueue文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName},ConsumeQueue具体目录结构如下所示:

图6  ConsumeQueue目录结构

        上述的TopicTest是topic名,TopicTest下有4个文件夹,分别代表不同的queueId。每个queueId下存储具体的ConsumeQueue文件,如上图的00000000000000000000文件。消费者在读取消息时,先读取ConsumeQueue,再通过ConsumeQueue中的位置信息读取CommitLog,得到原始的消息。

2、Index

        IndexFile(索引文件)用于为生成的索引文件提供访问服务。Index文件的存储位置是:$HOME \store\index\${fileName},它通过MsgId或消息Key值查询消息真正的实体内容。在实际的物理存储上,文件名则是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为420M,一个IndexFile可以保存 2000W个索引。IndexFile的文件结构如下所示:

图7 IndexFile文件结构

1)IndexHead 数据 

        IndexHead包含了6个部分:

  1. beginTimestamp:该IndexFile包含消息的第一条消息的存储时间。大小为8字节。
  2. endTimestamp:该IndexFile包含消息的最后一条消息的存储时间。大小为8字节。
  3. beginPhyoffset:第一条消息在commitlog的偏移量。大小为8字节。
  4. endPhyoffset:最后一条消息在commitlog的偏移量。大小为8字节。
  5. hashSlotCount:已经填充的slot数。大小为4字节。
  6. indexCount:该IndexFile包含的索引个数。大小为4字节。

2)Hash槽

        Hash槽的作用就是存放当前slot下最新index的序号。每当放一个新的消息的index进来,首先取MessageKey的hashCode,然后用hashCode对slot总数取模,得到应该放到哪个slot中,slot总数默认500W个。只要是取hash就必然面临hash冲突的问题,跟HashMap一样,IndexFile也是使用一个链表结构来解决hash冲突。只是这里跟HashMap稍微有点区别的地方是,slot中放的是最新index的指针。这是因为一般查询的时候肯定是优先查最近的消息。

 

图8 IndexFile存储示意图 

        从上图可以看出,IndexFile结构与hash表很相似,固定数量的slot组成数组,每个slot对应一条index链,index之间通过链表方式组织在一起。slot的值对应当前slot下最新的那个index的序号,而index中存储了当前slot下以及当前index的前一个index序号,这样就把slot下的所有index链起来了。

3)Index 条目列表

        每个Index条目固定长度为20字节,存放真正的索引数据。Index总共大概有2000W,也就是说平均每个slot存4个Index。Index组成结构包含下列几种:

  1. hashcode:消息key的hashcode。
  2. phyoffset:消息对应的物理偏移量。
  3. timedif:该消息存储时间与第一条消息的时间戳的差值,小于0表示该消息无效。
  4. preIndexNo:该条目的前一条记录的 Index 索引,hash冲突时,根据该值构建链表结构。

五、总结

        本文主要介绍RocketMQ的消息存储,RocketMQ消息分为物理存储(CommitLog)和逻辑存储(ConsumeQueue及Index),最开始介绍了业界常用的消息存储类型,并对这些消息存储方式进行了对比。接下来分别介绍CommitLog、ConsumeQueue及Index。通过本文可以了解到RocketMQ在消息存储设计方面的巧妙性:通过物理存储提高Producer端发送消息的速度;通过ConsumeQueue提高Consumer端消费消息的速度;Index文件提供了一种根据key或者时间来查询具体消息的渠道。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值