RabbitMQ实战指南——存储机制总结

背景

之前了解过kafka和rocketMq的数据存储机制,知道在他们的存储机制上,支持消息的回溯消费、顺序消费,消息积压。而rabbitMq是不支持刚提到的三种特性,那rabbitMq的数据又是怎样存储的呢?

持久层

持久层是一个逻辑上的概念,包含了两部分:rabbit_queue_index(队列索引)和rabbit_msg_store(消息存储)。

rabbit_queue_index

rabbit_queue_index负责维护存储的消息的信息,包含了存储地址、是否已经交付给消费者、消费者是否已经ack等,职责有点像mysql的索引,但是多了消息状态的维护。
每一个queue都会有与之对应的一个rabbit_queue_index。

rabbit_msg_store

rabbit_msg _store以键值对的形式存储消息,整个node只有一个,被所有的queue共享。F又可以细分为msg_store_persistent和msg_store_transient,从名字很容易看出来,msg_store_persistent是负责持久化消息的存储,node重启消息不会丢失;msg_store_transient是负责非持久化消息的存储,node重启后消息丢失。

目录结构

默认在$RABBITMQ HOME/var/lib/mnesia/rabbit@
$HOSTNAME/路径下包含 queues、msg_store_persistent、msg_store_transient文件夹:
在这里插入图片描述

消息存储

消息都存储在rabbit_msg_store么?
消息不是一定需要存储在rabbit_msg_store,rabbit也支持直接把消息存储在rabbit_queue_index。
最佳的方式是较小的消息直接存储在rabbit_queue_index,较大的消息存储在rabbit_msg _store。消息应该放在rabbit_queue_index还是rabbit_msg _store是根据queue_index_embed_msgs_below配置来决定的,默认是4096B,当消息的header,body和property总大小小于该配置,则消息直接存储在rabbit_queue_index。

为什么要将较小的消息存储在rabbit_queue_index中呢?
目的是为力提高性能!正常查找一个消息,是需要在rabbit_queue_index找到消息的存储位置,再去rabbit_msg _store里找到消息,需要一次中转,把消息直接放到rabbit_queue_index中,可以节省这一步。

rabbit_queue_index数据存储
rabbit_queue_index以顺序(文件名从0开始累加)的段文件来进行数据存储,后缀为“.idx”。大致效果如下:

  • xxx0.idx
  • xxx1.idx
  • xxx2.idx
    每个段文件固定存储SEGMENT_ENTRY_COUNT条记录,默认值为16384。rabbit_queue_index要从磁盘读取数据的时候,至少要在内存维护一个段文件,也就是说,需要把整个段文件加载到内存,再读取段里面的内容。
    由于这样的机制,调整SEGMENT_ENTRY_COUNT和queue_index_embed_msgs_below两个的值需要注意,很容易引发内存的暴增。
    SEGMENT_ENTRY_COUNT翻倍,每个段大小翻倍,queue_index_embed_msgs_below翻倍,每个段大小也翻倍。

rabbit_msg_store数据存储
由rabbit_msg_store负责存储的消息,都是以追加的方式写入到文件中,当文件大小超过file_size_limit之后,就会关闭这个文件,创建一个新的文件给新消息写入。文件以“.rdq”结尾,从0开始累加,如下:

  • xxx0.rdq
  • xxx1.rdq
  • xxx2.rdq

从rabbit_msg_store读取消息:
RabbitMQ在ETS(Erlang Term Storage)表中记录消息在那个rdq文件,在文件中的哪个位置。
读取消息的时候,先根据消息id,从ETS中找到存储的信息,并找到对应的文件,如果文件存在并且没有被锁住,则直接打开文件,从指定位置读取消息的内容。如果文件不存在或者被锁住,则交给rabbit_msg_store处理。

rabbit_msg_store删除数据
删除消息,只是从ETS表删除指定消息相关的存储信息,同时更新存储消息的文件,把消息标记为“垃圾数据”。也就是说,不会立即把消息从文件中删除,等文件中存储的数据都是“垃圾数据”,就可以将该文件删除。当**前后(相邻)**两个文件的有效数据可以合并成一个文件,并且“所有垃圾数据的大小/所有文件的数据大小>GARBAGE FRACTION”时,会触发两个文件合并成一个。

队列中消息的状态

  • alpha:消息内容(包括消息体、属性和headers)和消息索引都存储在内存中。最消耗内存,很少消耗CPU,不需要磁盘IO。
  • beta:消息内容保存在磁盘中,消息索引保存在内存中。消耗的内存不多,只有索引在内存中,需要一次磁盘IO。
  • gramma:消息内容保存在磁盘,消息索引在内存和磁盘中都有。消耗的内存不多,只有索引在内存中,需要一次磁盘IO。
  • delta:消息内容和索引都在磁盘中。基本不消耗内存,但是需要消耗最多的CPU和两次磁盘IO,一次从磁盘读索引,一次读消息内容。

对于持久化的消息,消息内容和消息索引都必须保存在磁盘上,才会处于上述状态中的一种。
gamma状态的消息是只有持久化的消息才会有的。
这个结论要成立,需要满足:非持久化的消息,内存不足也不会把索引持久化到磁盘。

RabbitMq会根据消息传递速度定期计算出一个当前内存能够保存的最大消息数量。如果alpha状态的消息数量大于这个值,就会将多出的消息转换到beta状态,gramma状态或delta状态。

队列结构

在这里插入图片描述

队列通常由rabbit_amqqueue_process和backing_queue两部分组成:rabbit_amqqueue_process
负责协议相关的处理,即接收生产者发布消息,想消费者交付消息、处理消息的确认(生产者的confirm和消费者的ack)
backing_queue
负责消息存储的具体形式和引擎,向rabbit_amqqueue_process提供相关的接口调用。
对于普通的没有设置优先级和镜像的队列来说,backing_queue的默认实现是rabbit_variable_queue,内部由五个部分组成

  • Q1,只存储alpha状态的消息
  • Q2,存储beta和gramma状态的消息
  • Delta,只存储delta状态的消息
  • Q3,存储beta和gramma状态的消息
  • Q4,只存储alpha状态的消息
    一般情况下,消息按照Q1->Q2->Delta->Q3->Q4的顺序流转,但不是每个消息都必须经历所有的状态,这个取决当前系统的负载,系统负载低的时候,可能Q1->Q4,不需要经过Q2、Delta、Q3。

取消息的时候,逻辑如下:

  • 优先从Q4取消息
  • Q4为空,从Q3取消息
    • 如果Q3不为空,从Q3取出消息,然后判断Q3和Delta的长度
    • 如果Q3为空,Delta不为空,将Delta数迁移到Q3
      • Delta迁移数据到Q3是按分段读取,判断段里的消息数量是否与Delta消息数相等。如果相等,证明Delta被清空,可以把Q2的数据直接迁移到Q3
    • 如果Q3和Delta都为空,则代表Q2、Delta、Q3和Q4都为空,Q1数据可以直接迁移到Q4
  • Q3 为空,则整个队列为空

对于“Q3为空,则整个队列为空”,光上面的信息,是不成立的。例如我现在整个队列都是空的,然后来了一个消息,这个消息直接进入Q1,那Q3是空的,但是队列不空呀~~
需要加上下面的原因:
如果消息投递的目的队列是空的,并且有消费者订阅了这个队列,那么该消息会直接发送给消费者,不会经过队列这一步。

惰性队列

上面说了队列的结构和消息的状态,可以了解到,rabbitMQ其实是很依赖内存的,普通的队列,当消息堆积的时候,rabbitMQ会把消息持久化到磁盘,从导致处理能力下降。
惰性队列就是为了应对消息堆积的场景而设计出来的。惰性队列会将接收到的消息存入文件系统中,而不管是持久化还是非持久化消息。这样可以减少内存的消耗,但是会增加磁盘的IO。
如果发的是持久化消息,那么这种磁盘IO是不可以避免的,所以发送持久化消息的时候,使用惰性队列,不会产生额外开销。
如果惰性队列存储非持久化消息,内存使用率会一直很稳定,因为消息会被持久化,但是重启后消息一样会丢失。
没太明白,为什么持久化了,还会丢失??难道只持久化消息内存,没持久化索引。重启后,内存中的索引丢失,所以找不到原来的消息??

官方数据:
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值