rabbitmq的持久化分为三个部分:
消息的持久化可以通过消息的投递模式来实现,属于代码层面上的。
在web监控页面没有指定持久化的属性参数,但是消息一般都是要设置持久化的,所以在web页面我们可以看到发送消息时下面都带有持久化标识的。
但是将所有消息都设置为持久化,会严重影响rabbitmq服务器性能,写入磁盘的速度比写入内存的速度慢得不只一点点。所以对于可靠性不是那么高的消息可以不采用持久化处理以提高整体的吞吐量。在选择是否要将消息持久化时,需要在可靠性和吐吞量之间做一个权衡。
代码设置消息的持久化
将交换器、队列、消息都设置了持久化之后也不能百分之百保证数据的丢失。
从消费者来说,如果在订阅消费队列时将 autoAck 参数设置为 true,那么当消费者接收到相关消息之后,还没来得及处理就宕机了,这样也算数据丢失。这种情况很好解决,将autoAck 参数设置为 false,并进行手动确认。
在持久化的消息正确存入rabbitmq之后,还需要有一段时间(虽然很短,但是不可忽视) 才能存入磁盘之中。如果在这段时间内rabbitmq服务节点发生了宕机、重启等异常情况,消息保存还没来得及落盘,那么这些消息将会丢失。这种情况可以使用镜像队列来解决。
前面提到的消息持久化,其实是在rabbitmq的“持久层”中完成的。不管是持久化的消息,还是非持久化的消息都可以被写入到磁盘。
持久层是一个逻辑上的概念,实际包含两个部分:
队列索引 (rabbit_queue_index):负责维护队列中落盘消息的信息,包括消息的存储地点、消息在队列中的位置、是否已被交付给消费者、是否已被消费者 ack 等。每个队列都有与之对应的一个队列索引。
消息存储(rabbit_msg_store):而消息存储是以键值对的形式存储消息,它被所有队列共享,所以在每个节点中有且只有一个。从技术层面上来说,rabbit_msg_store 具体还可以分两类:
存在队列索引里的好处?
性能上的优化。相比存在消息存储里,直接存在队列索引仅需进行一次写操作。而存储在消息存储中的消息则需要两次写操,先写一次索引,再写一次消息存储,因此会有一定的性能提升。
注意事项:
若消息直接存在队列索引中,则当消息通过exchange同时路由到多个队列时,此消息会被写到每个队列的索引文件中。
若消息是存在消息存储中,就仅仅只有一个副本。
I have no name!@ed73deb9f1c5:/bitnami/rabbitmq/mnesia/rabbit@stats/msg_stores/vhosts/9PIHRMVSJH6VBOR100H7141ZT$ ls -al
total 16
drwxr-xr-x. 5 1001 root 125 Oct 7 02:57 .
drwxr-xr-x. 4 1001 root 72 Oct 7 01:15 ..
-rw-r--r--. 1 1001 root 83 Oct 7 01:15 .config
drwxr-xr-x. 2 1001 root 19 Oct 7 02:57 msg_store_persistent
drwxr-xr-x. 2 1001 root 19 Oct 7 02:57 msg_store_transient
drwxr-xr-x. 3 1001 root 38 Oct 7 01:18 queues
-rw-r--r--. 1 1001 root 5464 Oct 7 02:57 recovery.dets
-rw-r--r--. 1 1001 root 9 Oct 7 02:57 .vhost
rabbit_queue_index 中以顺序(文件名从 0 开始累加) 的段文件来进行存储,后缀为“ .idx "。
每个段文件中包含定的 SEGMENT_ENTRY_COUNT 条记录,SEGMENT_ENTRY_COUNT 默认值为16384字节。
每个rabbit_queue_index 从磁盘中读取消息的时候至少要在内存中维护一个段文件,所以设置queue_index_embed_msgs_below参数指定阈值大小时要格外谨慎,一点点增大也可能会引起内存爆炸式的增长。
经过 rabbit_msg_store 处理的所有消息都会以追加的方式写入到文件中,当一个文件的大小超过指定的限制 (file_size_lmit)后,关闭这个文件再创建一个新的文件以供新的消息写入,文件后缀是“ .rdq ”。
文件名从0开始进行累加,所以文件名最小的文件也是最老的文件。
如下所示0.rdq文件
I have no name!@ed73deb9f1c5:/bitnami/rabbitmq/mnesia/rabbit@stats/msg_stores/vhosts/628WB79CIFDYO9LJI6DKMI09L/msg_store_persistent$ ls -al
total 0
drwxr-xr-x. 2 1001 root 19 Oct 7 02:57 .
drwxr-xr-x. 4 1001 root 111 Oct 7 02:57 ..
-rw-r--r--. 1 1001 root 0 Oct 7 02:57 0.rdq
在进行消息的存储时,rabbitmq会在ETS (Erlang Term Storage) 表中记录消息在文件中的位置映射 (Index) 和文件的相关信息 (FileSummary)。
文件合并前提:
执行合并的两个文件一定是逻辑上相邻的两个文件。
文件合并流程:
老版本中,都是把较小的消息存储在rabbit_queue_index中,较大的消息存储在rabbit_msg_store中。通过queue_index_embed_msgs_below参数指定消息大小,默认值为4096字节。即消息体、属性以及headers长度累加起来整体小于4096字节的消息将直接存储在队列索引中。
而从3.5.0版本开始,较小的消息是直接存储在队列索引中。
如下图所示,我发布的消息,消息比较小时,在0.idx中,即存在索引中
当消息体比较大时,存放的是rdq文件时面
在进行消息的存储时,rabbitmq会在ETS表中记录消息在文件中的映射,以及文件的相关信息。
消息读取时,根据消息ID找到该消息所存储的文件,在文件中的偏移量,然后打开文件进行读取。
消息的删除只是从ETC表删除指定消息的相关信息,同时更新消息对应存储的文件的相关信息(更新文件有效数据大小)。
每个队列则看成是一个客户端,当生产者发送的消息达到队列时,向服务端请求写,写入过程如下:
前文提到ETS,ETS是啥咱们来了解一下,这是偏开发向的,运维可以了解一下。
rabbitmq在其内部维护了多张表,每张表作用不同,对应记录的内容也都不同。
有的是记录消息与存储文件的相关信息:消息存储在哪个文件中、在文件中的偏移位置、消息的长度、引用次数、总共有多少个文件、文件中有多少有效消息、左右关联的文件信息等。
有的则是用于操作记录、消息缓存等,以便于减少对文件的实际读写操作。
1.第一张表:flying_ets表。
表的作用:
记录消息write、remove的引用计数。
表结构参数释义:
CRef参数:表示客户端对应的reference,每个客户端唯一。
MsgId参数:表示消息的唯一ID。
Count参数:表示消息的引用计数。
第二张表:cur_file_cache_ets表。
表的作用:
记录当前正在写的文件的消息缓存计数。
表结构参数释义:
MsgId参数:表示消息的唯一ID。
Msg参数:表示消息内容。
Count参数:表示消息的引用计数。
第三张表: msg_store_ets_index表。
表的作用:
记录消息在文件中的索引信息。
表结构参数释义:
MsgId参数:表示消息的唯一ID。
RefCount参数:表示消息的计数。
File参数:表示消息所在的存储文件名称。
Offset参数:表示消息在文件中的偏移位置。
TotalSize参数:表示消息的长度。
表的作用:
记录文件的描述信息。
表结构参数释义:
File参数:表示存储文件名称。
ValidTotalSize参数:表示存储文件的有效数据大小。
Left参数:表示位于该文件左边的文件是哪个。
Right参数:表示位于该文件右边的文件是哪个。
FileSize参数:表示文件总体大小。
Locked参数:表示文件锁定标记,文件合并或删除前会进行锁定。
Readers参数:表示当前正在读该文件的客户端个数。
队列的组成:
消息的4 种状态:
注意事项:
rabbitmq在运行时,会根据统计的消息传送速度,定期计算一个当前内存中能够保存的最大消息数量(target_ram_count)。
消息状态对服务器资源的影响:
注意事项:
消费消息对队列中的消息状态变化流程:
结论:
影响及应对措施:
对运维来说,中间件对服务器资源状态的影响需要严格控制,所以本小结就非常重要了。
当内存使用超过配置的阈值或者磁盘剩余空间低于配置的阈值时,rabbitmq都会暂时阻塞(block) 客户端的连接(Connection)并停止接收从客户端发来的消息,以此避免服务崩溃。同时,客户端与服务端的心跳检测也会失效。可以通过命令或者 Web 管理界面来查看它的状态。
Connection阻塞状态有两种:
blocking状态:对应于并不试图发送消息的 Connection,比如消费者关联的 Connection,这种状态下的 Connection 可以继续运行。
blocked状态:对应于一直有消息发送的 Connection,这种状态下的 Connection 会被停止发送消息。
注意事项:
注意在一个集群中,如果一个 Broker 节点的内存或者磁盘受限,都会引起整个集群中所有的Connection 被阻塞。
[root@node1 rabbitmq]# rabbitmqctl list_connections
内存告警:
所谓内存告警,就是当rabbitmq使用的内存资源超过了在配置文件中设置的阈值,就会触发告警,此时会阻塞所有生产者的连接。
当告警被解除,比如积压的消息被消费、消息从内存写入磁盘磁盘释放内存资源等情况,一切都会恢复正常。
内存阈值:
默认情况下vm_memory_high_watermark.relative的值为0.4,即内存阈值为 0.4,表示当rabbitmq使用的内存超过 40%时,就会产生内存告警并阻塞所有生产者的连接。
rabbitmq默认使用内存阈值为40%,只是代表当超过这个值时,会限制生产者的连接,不代表此时的rabbitmq不能使用超过 40%的内存。
在最坏的情况下,Erlang的垃圾回收机制会导致两倍的内存消耗,也就是 80%的使用占比。
在3.11.4版本中,启动rabbitmq服务时,打印得日志可以看出,默认使用的内存阈值时0.4。
官方建议设置此值区间为:0.4~0.66 ,不建议取值超过 0.7。
配置文件中设置需要重启rabbitmq服务才能生效,相比下文中的命令设置,服务器重启也同样生效。所以生产环境中建议是在配置文件中修改。
1.此时内存阈值为默认0.4,日志显示。
2.修改配置文件内存阈值参数vm_memory_high_watermark.relative,重启服务,此时日志显示就是0.6的配置了。
3.在监控页面也可以看到阈值
上面是通过占用服务器总内存的百分比设置,也可以直接用指定配置参数vm_memory_high_watermark.absolute用绝对值设置,默认单位为 B,也可以指定单位设置。
内存阈值绝对值怎么算?
假设服务器内存为4GB,则rabbitmq服务的内存值的绝对值为4GBx0.4=1.6GB。如果是 32 位的 Windows 操作系统,那么可用内存被限制为 2GB,也就意味着 RabbitMQ 服务的内存闽值的绝对值为 820MB 左右。
1.修改配置文件参数,取消注释,重启服务,查看日志。
2.监控页面也是即时更改过来。
也可以不使用字节来设置,换算太麻烦,直接指定GB、MB等单位会更方便。
1.修改配置文件参数vm_memory_high_watermark.absolute,指定设置内存阈值绝对值为1GB,重启服务。
2.重启服务,查看日志显示为1G。
绝对值设置:rabbitmqctl set_vm_memory_high_watermark absolute {memory limit}
{ memory limit}参数,代表设置的内存阈值绝对值大小,可以指定计量单位,GB、KB等,默认使用的是字节单位。
百分制设置:rabbitmqctl set_vm_memory_high_watermark {fraction]
{fraction]参数,代表设置的内存阈值百分比大小。
1.使用绝对值命令设置内存阈值大小为2GB。
#指定单位。
[root@node1 rabbitmq]# rabbitmqctl set_vm_memory_high_watermark absolute 2GB
Setting memory threshold on rabbit@node1 to 2GB bytes ...
3.不指定计量单位设置时,使用的是字节计量单位。如下设置为1GB,不指定计量单位。
#不指定单位设置。
[root@node1 rabbitmq]# rabbitmqctl set_vm_memory_high_watermark absolute 1073741824
4.使用百分制命令设置内存阈值为0.6。
#使用百分制设置。
[root@node1 rabbitmq]# rabbitmqctl set_vm_memory_high_watermark 0.6
在某个 Broker 节点触及内存并阻塞生产者之前,它会尝试将队列中的消息换页到磁盘以释放内存空间。持久化和非持久化的消息都会被转储到磁盘中,其中持久化的消息本身就在磁盘中有一份副本,这里会将持久化的消息从内存中清除掉。
设置参数:vm_memory_high_watermark_paging_ratio。
当设置为大于 1 的浮点数,这种配置相当于禁用了换页功能。
默认情况下,在内存到达内存闯值的 50%时会进行换页动作。也就是说,在默认的内存阙值为 0.4 的情况下,当内存超过 0.4X0.5=0.2 时会进行换页动作。
设置参数:vm_memory_calculation_strategy。
’ 默认为rss策略,可选策略有:allocated 、 rss 、 legacy 。
设置参数:memory_monitor_interval,单位毫秒。
磁盘告警:
当剩余磁盘空间低于配置的阈值时,rabbitmq也会阻塞生产者,这样可以避免因非持久化的消息持续换页,从而耗尽磁盘空间导致服务崩溃。
配置参数:disk_free_limit.absolute
默认情况下,磁盘阈值为 50MB,表示当磁盘剩余空间低于50MB时,会阻塞生产者并停止内存中消息的换页动作。
注意事项:
这个阙值的设置可以减小,但不能完全消除因磁盘耗尽而导致崩溃的可能性,比如在两次磁盘空间检测期间内,磁盘空间从大于 50MB 被耗尽到 OMB。
一个相对谨慎的做法是将磁盘阙值设置为与操作系统所显示的内存大小一致。
定期检查磁盘剩余空间频率:
rabbitmq会定期检测磁盘剩余空间,检测频率与上一次执行检测到的磁盘剩余空间大小有关。
正常情况下,每 10 秒执行一次检测,随着磁盘剩余空间与磁盘闽值的接近,检测频率会有所增加。
当要到达磁盘闽值时,检测频率为每秒 10 次,这样有可能会增加系统的负载。
默认设置的是50MB,单位字节。服务启动时也会显示此设置。
也可以指定单位设置,GB、mb、KB。
还可以根据服务器内存的大小和磁盘阈值设置一个相对的比值。
设置参数:disk_free_limit.relative
正常情况下,建议 disk free limit.mem relative 的取值为 1.0 和2.0之间。
命令设置,在服务器重启后失效,不要用,只做知识了解。
绝对值设置: rabbitmqctl set_disk_free_limit {disk_limit}
相对值设置: rabbitmqctl set_disk_free_limit mem_relative {fraction}
{fraction}参数代表,服务器内存大小和磁盘阈值的比值。
1.设置为2GB,查看日志。不指定单位默认使用字节单位。
[root@node1 rabbitmq]# rabbitmqctl set_disk_free_limit 2GB
Setting disk free limit on rabbit@node1 to 2GB bytes ...
2.默认使用字节单位。
3.相对值设置。
#将磁盘阈值设置于服务器内存一样大。
[root@node1 rabbitmq]# rabbitmqctl set_disk_free_limit mem_relative 1.0
Setting disk free limit on rabbit@node1 to 1.0 times the total RAM ...
本文参考自:CSDN博主「百慕卿君」的原创文章,原文链接
https://blog.csdn.net/yi_qingjun/article/details/128448658