【背景】
在实际使用过程中,发现有时候rabbitmq启动后,很快就能提供服务(在指定端口上侦听,客户端能正常连接到rabbitmq);而有时候则需要过一段时间才能提供服务,尤其是启动前有许多持久化的消息未被消费掉的时候。在这种情况下,日志文件中可以看到如下类似的日志信息:
从日志的字面意思来看是需要进行索引的重建工作,那么,什么时候需要进行索引的重建,什么时候不需要进行索引的重建?索引重建具体是怎样进行的呢?作者通过源码分析与实际测试,进行了如下总结。
【索引重建相关的文件】
在rabbitmq中,持久化消息存储在后缀为rdq的文件中(msg_store的存储方式),消息在队列中的位置(索引)存储在后缀为idx的文件中(消息也可能直接存储在索引文件中,可以参考这篇文章),整体情况大概如下图所示:
由于在msg_store中存储的一条消息,可能会被多个队列引用,因此rabbitmq内部维护了几张表 ,用于记录相关信息。
当rabbitmq正常关闭时(通过rabbitmqctl stop),msg_store消息文件存储所在目录,除了存储消息的rdq文件外,还有其他几个文件:
其中“msg_store_index.ets”对应消息在文件中的索引信息表中的记录信息;“file_summary.ets”对应文件概述表中的记录信息;“clean.dot”为元数据文件,该文件记录了持久化队列的信息以及维护消息索引的模块名称信息,该文件中的内容大概是这样的:
注:对于采用msg_store方式存储的消息,每个队列都作为客户端向msg_store的服务进程发送消息的读写请求,队列发送请求之前会像服务进程进行注册,注册时就会带上客户端的reference信息,也就是队列的唯一ID;文件中记录的client_refs也就是每个队列的唯一ID。因此可以说clean.dot包含了各个队列的信息。
另外,针对每个vhost,rabbitmq正常关闭时,还会生成一个“recovery.dets”文件。该文件记录了每个队列的概要信息:包括队列的reference(唯一ID),持久化消息数,持久化消息字节数,每个索引文件中unack的消息数。例如:
注意,这里可以看到队列的persistent_ref信息也在“clean.dot”文件中的client_refs也有记录。
上述所说的几个文件,这里统称为recovery文件,这些文件存在的目的就是方便快速的完成索引重建。
【重建流程】
rabbitmq启动时,针对每个vhost,会读取vhost对应的“recovery.dets”文件,从中获取有效队列的相关信息;然后启动msg_store的服务进程,在服务进程中会加载“file_summary.ets”,“clean.dot”,“msg_store_index.ets”文件中的内容。
如果“recovery.dets”中拿到的有效队列的唯一ID,与“clean.dot”中记录的队列信息完全匹配,同时“file_summary.ets”与“msg_store_index.ets”也都正确加载,这意味着可以直接根据这些文件中记录的信息完成索引的重建;否则,则需需要遍历读取所有的消息文件(*.rdq)、队列的索引文件(*.idx)来完成索引的重建工作。
异常情况下,比如断电,或者直接将rabbitmq的进程kill掉,那么上面提到的recovery文件就不会存在(正常关闭时才生成,启动加载后会删除这些文件)或者文件不完整。这种情况下,recovery文件会加载失败,因此就需要遍历所有的消息文件与队列索引文件进行索引的重建。
在分析相关源码后,发现,这种情况下的索引重建,队列索引文件(*.idx)会被读取两次。第一次读取用于帮助msg_store中消息的正确引用计数,第二次读取则是用于恢复每个队列本身的队列索引信息。因此这将会是一个很耗时的动作。
【总结】
尽可能优雅(正常)的关闭rabbitmq,这样启动时就不会有索引重建的耗时工作了。