RabbitMQ的数据存储

存储机制

        RabbitMQ消息有两种类型:

                1、持久化消息和非持久化消息

                2、这两种消息都会被写入磁盘

        持久化消息在到达队列时写入磁盘,同时会在内存中会保存一份备份,当内存吃紧时,消息从内存中清除,这会提高一定的性能。

        非持久化消息一般只存于内存中,当内存压力大时数据刷盘处理,以节省内存空间。

         RabbitMQ存储层包含两个部分:队列索引和消息存储

队列索引:rabbit_queue_index

        索引维护队列的落盘消息的信息,如存储地点、是否已被消费者接收,是否已被消费者ack等

        每个队列都有相对应的索引

        索引使用顺序的段文件来存储,后缀为.idx,文件名从0开始累加,每个段文件中包含固定segment_entry_count条记录,默认值是16384。每个index从磁盘中读取消息的时候,至少要在内存中维护一个段文件,所以设置queue_index_embed_msgs_below值的时候要格外谨慎,一点点增大也可能会引起内存爆炸式增长。

消息存储:rabbit_msg_store

        消息以键值对的方式存储到文件中,一个虚拟主机的所有队列使用同一块存储,每个节点只有一个。存储分为持久化存储(msg_store_persistent)和短暂存储(msg_store_transient)。持久化存储的内容在broker启动后不会丢失,短暂存储的内容在broker启动后丢失。

        store使用文件来存储,后缀为.rdq,经过store处理的所有消息都会以追加的方式写入到该文件中,当该文件的大小超过指定的限制(file_size_limit)后,将会关闭该文件并创建一个新的文件以供新的消息写入。文件名0开始进行累加。在进行消息的存储时,RabbitMQ会在ETS(Erlang Term Storage)表中记录消息在文件中的位置映射和文件的相关信息。

        消息(包括消息头、消息体、属性)可以直接存储在index中,也可以存储在store中。最佳的方式是较小的消息存储在index中,而较大的消息存储在store中。这个消息大小的界定可以通过rabbitmq.conf文件中queue_index_embed_msgs_below来配置,默认值为4096B。当一个消息小于设定的大小阈值时,就可以存储在index中,这样性能上可以得到优化。一个完整的消息大小小于这个值,就放到索引中,否则放到持久化消息文件中(msg_store_persistent目录中的.rdq)

        读取消息时,先根据消息的ID(msg_id)找到对应存储的文件,如果文件存在并且未被锁定,则直接打开文件,从指定位置读取消息内容。如果文件不存在或者被锁定了,则发送请求由store进行处理。

        删除消息时,只是从ETS表删除指定消息的相关信息,同时更新对应消息的存储文件和相关信息。在执行消息删除操作时,并不立即对文件中的消息进行删除,也就是说消息依然在文件中,仅仅是标记为垃圾数据而已。当一个文件中都是垃圾数据时可以将这个文件删除。当检测到前后两个文件中的有效数据可以合并成一个文件,并且所有的垃圾数据的大小和所有文件(至少有三个文件的情况下)的数据大小的比值超过设置的阈值garbage_fraction(默认值0.5)时,才会触发垃圾回收,将这两个文件合并,执行合并的两个文件一定是逻辑上相邻的两个文件。合并逻辑:

                1、锁定这两个文件

                2、先整理前面的文件的有效数据,再整理后面的文件的有效数据

                3、将后面的文件的有效数据写入到前面的文件中

                4、更新消息在ETS表中的记录

                5、删除后面的文件

队列结构

        通常队列由rabbit_amqqueue_process和backing_queue这两部分组成,rabbit_amqqueue_process负责协议相关的消息处理,即接收生产者发布的消息、向消费者交付消息、处理消息的确认(包括生产端的confirm和消费端的ack)等。backing_queue是消息存储的具体形式和引擎,并向rabbit_amqqueue_process提供相关的接口以供调用。

        如果消息投递的目的队列是空的,并且有消费者订阅了这个队列,那么该消息会直接发送给消费者,不会经过队列这一步。当消息无法直接投递给消费者时,需要暂时将消息存入队列,以便重新投递。

        rabbit_variable_queue.erl源码中定义了RbbitMQ队列的四种状态:

                1、alpha:消息索引和消息内容都存内存,最耗内存,很少消耗cpu

                2、beta:消息索引存内存,消息内容存磁盘

                3、gama:消息索引内存和磁盘都有,消息内容存磁盘

                4、delta:消息索引和内容都存磁盘,基本不消耗内存,消耗更多cpu和I/O操作

        消息存入队列后,不是固定不变的,它会随着系统的负载在队列中不断流动,消息的状态会不断发生变化。

        持久化的消息,索引和内容都必须先保存在磁盘上,才会处于上述状态中的一种

        gama状态只有持久化消息才会有的状态

        在运行时,RabbitMQ会根据消息传递的速度定期计算一个当前内存中能够保存的最大消息数量(target_ram_count),如果alpha状态的消息数量大于此值,则会引起消息的状态转换,多余的消息可能会转换到beta、gama或者delta状态。区分这四个状态的主要作用是满足不同的内存和cpu需求。

        对于普通没有设置优先级和镜像的队列来说,backing_queue的默认实现是rabbit_variable_queue,其内部通过5个子队列Q1、Q2、delta、Q3、Q4来体现消息的各个状态。

        消费者获取消息也会引起消息的状态转换,当消费者获取消息时:

                1、首先会从Q4中获取消息,如果获取成功则返回

                2、如果Q4为空,则尝试从Q3中获取消息,系统首先会判断Q3是否为空,如果为空则返回队列为空,即此时队列中无消息。

                3、如果Q3不为空,则取出Q3中的消息;进而再判断此时Q3和delta中的长度,如果都为空,则可以认为Q2、delta、Q3、Q4全部为空,此时将Q1中的消息直接转移至Q4,下次直接从Q4中获取消息。

                4、如果Q3为空,delta不为空,则将delta的消息转移至Q3中,下次可以直接从Q3中获取消息。在将消息从delta转移到Q3的过程中,是按索引分段读取的,首先读取某一段,然后判断读取的消息的个数与delta中消息的个数是否相等,如果相等,则可以判定此时delta中已无消息,则直接将Q2和刚读取到的消息一并放入到Q3中,如果不相等,仅将此次读取到的消息转移到Q3。

        这里有两处疑问,第一个疑问是:为什么Q3为空则认定整个队列为空?

                1、试想一下,如果Q3为空,delta不为空,那么在Q3取出最后一条消息的时候,delta中的消息就会被转移到Q3,这样与Q3为空矛盾。

                2、如果delta为空且Q2不为空,则在delta取出最后一条消息时会将Q2的消息并入到Q3中,这样也与Q3为空矛盾。

                3、在Q3取出最后一条消息之后,如果Q3、delta都为空,且Q1不为空时,则Q1的消息会被转移到Q4,这与Q4为空矛盾。

        通常在负载正常时,如果消费速度大于生产速度,对于不需要保证可靠不丢失的消息来说,极有可能只会处于alpha状态。

为什么消息的堆积导致性能下降?

        在系统负载较高时,消息若不能很快被消费掉,这些消息就会进入到很深的队列中去,这样会增加处理每个消息的平均开销。因为要花更多的时间和资源处理"堆积"的消息,如此用来处理新流入的消息的能力就会降低,使得后流入的消息又被积压到很深的队列中,继续增大每个消息的平均开销,继而情况变得越来越恶化,使得系统的处理能力大大降低。

        应对这一问题一般有3种措施:

                1、增加prefetch_count的值,即一次发送多条消息给消费者,加快消息被消费的速度。

                2、采用multiple ack,降低处理ack带来的开销

                3、流量控制

       

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RabbitMQ是一个开源的消息队列中间件,它主要用于在应用程序之间传递消息。RabbitMQ并不直接提供数据存储的功能,它更专注于消息传递和队列管理。 然而,你可以通过在消息中包含数据,将数据存储RabbitMQ中。当一个应用程序想要将数据发送给另一个应用程序时,它可以将数据封装为消息并发送到RabbitMQ的队列中。接收方应用程序可以从队列中获取消息,并处理其中的数据。 如果你需要持久化存储数据而不仅仅是传递消息,你可以考虑以下几种方式: 1. 数据库:将数据存储在关系型数据库(如MySQL、PostgreSQL)或非关系型数据库(如MongoDB、Redis)中。应用程序可以使用RabbitMQ数据发送给一个专门负责存储数据的服务,该服务将数据保存到数据库中。 2. 文件系统:将数据存储在文件系统中,例如本地磁盘或分布式文件系统(如HDFS)。应用程序可以使用RabbitMQ数据发送给一个负责将数据写入文件系统的服务。 3. 其他存储介质:根据具体需求,你还可以考虑使用内存数据库、对象存储、分布式存储等其他存储介质。 需要注意的是,RabbitMQ本身并不提供持久化功能,如果你希望消息在RabbitMQ意外关闭或重启后仍然可用,你需要将队列和消息设置为持久化。这样,即使RabbitMQ关闭,数据也不会丢失。 总之,RabbitMQ主要用于消息传递,但你可以使用其他存储解决方案将数据存储RabbitMQ之外。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值