日期: September 30, 2022
来自我不知何时遥远的笔记,虽然用处不大,但也算解惑了_
说起rabbitmq我经常第一时间想到的是内存,因为在我过往的使用经历中,最常见的就是mq节点内存爆了,而kafka不会,kafka一般是磁盘爆了。。
mq挂的时候我经常看mq的数据文件,希望找到点挽救手段(因为我一直不知道这些文件作用是啥)
Rabbitmq的存储机制
首先声明一点:rabbitmq不管是持久化的消息还是非持久化的消息都可以被写入到磁盘
持久化的消息在到达队列时就被写入到磁盘,如果可以,持久化的消息也会在内存中保存一份备份,这样可以提高一定的性能, 当内存吃紧的时候会从内存中清除***(所以mq队列堆积会有内存打爆的情况,因为你的持久化消息在内存和磁盘都有一份)***
非持久化的消息一般只保存在内存中,在内存吃紧的时候会被换入到磁盘中,节省内存空间
这一落盘过程在RabbitMQ的“持久层”中完成
持久到磁盘那肯定是以文件的形式了
所以经常能在mq的数据目录下看到这两个目录
队列索引(rabbit_queue_index) 和消息存储(rabbit_msg_store)
rabbit_queue_index
rabbit_queue_index 负责维护队列中落盘消息的存储地点、是否被消费者接收、是否被消费者ack等。每个队列都有一个对应的rabbit_queue_index
rabbit_msg_store
rabbit_msg_store目录下又有msg_store_ persistent和msg_store_transient两个目录,里面放的就是队列消息的数据了,以键值对的形式存储消息,它被所有队列共享,在每个节点中有且只有一个
msg_store_persistent进程负责持久化消息的持久化,重启后消息不会丢失
msg_store_transient 进程负责非持久化消息的持久化,重启后消息会丢失
注意
消息(包括消息体、属性和headers)也可以直接存储在rabbit_queue_index 中,也可能被存在rabbit_msg_store 中。这是mq的一个设计,较小的消息存储在rabbit_queue_index中,较大的消息存储在rabbit_msg_store 中,这个消息大小的界定可以通过queue_index_embed_msgs_below 来配置,默认大小为4096,单位为B,这里的消息大小是指消息体、属性及headers整体的大小
默认在HOSTNAME/路径下有queues、msg_ store_ persistent、 msg_ store_ _transient 这3个文件夹,其分别存储对应的信息
消息写入
经过rabbit_msg_store 处理的所有消息都会以追加的方式写入到文件*.rdq中,当一个文件的大小超过限制(file_ size_ limit) 后,mq会再创建一个新的文件以供新消息写入。.rdq文件从0开始进行累加,.idx 中也是以文件名从0开始累加的段文件的形式进行存储,idx 每个段文件中包含固定的SEGMENT_ENTRY_COUNT 条记录(默认值为16384)
这里在写入rdq文件前还会先经过一个buffer,大小为1M,消息在写入文件前,先写入这个Buffer,如果Buffer已满,则会将Buffer写入到文件(未必刷到磁盘)
固定的刷盘时间为25ms,不管Buffer满不满每隔25ms,Buffer里的数据及未刷新到磁盘的文件内容必定会刷到磁盘;或者在每次消息写入后,如果没有后续写入请求,则会直接将已写入的消息刷到磁盘
ETS表
在进行消息的存储时,RabbitMQ 会在ETS ( Erlang Term Storage)表中记录消息在文件中的位置映射(Index) 和文件的相关信息(FileSummary)
ETS表相关的文件有(均在rabbit_msg_store 目录下)
msg_store_index.ets:对应消息在文件中的索引信息表中的记录信息
file_summary.ets:对应文件概述表中的记录信息
clean.dot:为元数据文件,该文件记录了持久化队列的信息以及维护消息索引的模块名称信息
消息读取
读取消息时,先根据消息的ID (msg_ id) 找到对应存储的文件和偏移量,如果文件存在并且未被锁住,则直接打开文件,从指定位置读取消息的内容。如果文件不存在或者被锁住了,则发送请求由rabbit_msg_store 进行处理。每个rabbit_queue_index 从磁盘中读取消息时要在内存中维护一个段文件,这个段文件大小由queue_index_embed_msgs_below参数设置,一点点增大也可能会引起内存爆炸式的增长
消息删除
消息删除只是从ETS表删除指定消息的相关信息,同时更新消息对应的存储文件的相关信息。并不立即对在文件中的消息进行删除,消息依然在文件中,仅仅是标记为垃圾数据,当一个文件中都是垃圾数据时可以将这个文件删除
垃圾回收
当检测到前后两个文件中的有效数据可以合并在一个文件中,并且所有的垃圾数据的大小和所有文件(至少有3个文件存在的情况下)的数据大小的比值超过设置的阈值GARBAGE FRACTION (默认值为0.5)时才会触发垃圾回收将两个文件合并。合并时会首先锁定这两个文件,并先对前面文件中的有效数据进行整理,再将后面文件的有效数据写入到前面的文件,同时更新消息在ETS表中的记录,最后删除后面的文件
recovery.dets文件
在rabbitmq正常关闭时,每个vhost会生成一个”recovery.dets”文件(可恢复的元数据),它记录了每个队列的概要信息:队列的reference(唯一ID),持久化消息数,持久化消息字节数,每个索引文件中unack的消息数,然后在mq启动后读取这个文件,并加载“file_summary.ets”,“clean.dot”,“msg_store_index.ets”文件中的内容,如果“recovery.dets”中获取的队列的唯一ID,与“clean.dot”中的队列信息匹配,同时“file_summary.ets”与“msg_store_index.ets”也正确加载了,就可以根据这些文件中的信息完成索引重建;否则非正常关闭就不会生成这个文件,mq启动后就需要遍历读取所有的消息文件(.rdq)、队列的索引文件(.idx)来完成索引的重建,十分的耗时