持久化配置
概述
本指南涵盖了一些可配置的值,这些值会影响节点的吞吐量、延迟和 I/O 特性。在得出任何结论之前,请务必阅读整个指南,并熟悉使用 PerfTest 进行基准测试。
一些相关指南包括
RabbitMQ 中持久化的概述
现代 RabbitMQ 版本提供了多种队列类型,以及流
这些队列类型具有不同的存储实现,适用的配置设置也不同。
流
流使用基于日志的存储机制,在内存中只保留很少的数据(主要是尚未写入磁盘的操作数据)。尽管如此,它们在客户端使用RabbitMQ 流协议时,仍能提供出色的吞吐量。
由于流非常依赖磁盘 I/O,因此它们的吞吐量会随着消息量的增加而降低。它们可以从现代 SSD 和 NVMe 存储中获益良多。
流不提供与存储相关的可调存储参数。
仲裁队列
仲裁队列使用基于日志的存储机制,该机制由 RabbitMQ 的 Raft 实现提供。它们在内存中只保留很少的数据(主要是尚未写入磁盘的操作数据)。
由于仲裁队列会在执行任何操作之前将所有数据持久化到磁盘,因此建议使用尽可能快的磁盘。
由于仲裁队列的磁盘 I/O 密集型,因此它们的吞吐量会随着消息大小的增加而降低。
可能影响仲裁队列资源使用量的主要存储相关设置是预写日志段大小限制,即 WAL 内存表将移动到磁盘的限制。换句话说,在稳定负载下,每个仲裁队列都可以将最多此消息数据量保留在内存中。
可以通过以下方式控制此限制
# Flush current WAL file to a segment file on disk once it reaches 32 MiB in size
raft.wal_max_size_bytes = 32000000
由于内存释放可能需要一些时间,因此我们建议 RabbitMQ 节点的分配内存至少是默认 WAL 文件大小限制的 3 倍。高吞吐量系统需要更多内存。对于这些系统,建议分配 4 倍内存。
经典队列
经典队列可以使用两种存储实现:v1(最初的实现)和 v2(在 RabbitMQ 3.10 及更高版本中可用)。
队列版本
从RabbitMQ 3.10.0开始,代理有一个新的经典队列实现,名为版本 2。版本 2 队列具有新的索引文件格式和实现,以及新的每个队列存储文件格式,以取代将消息直接嵌入索引中的方式。
版本 2 的主要改进是在高内存压力下提高了稳定性。
在RabbitMQ 3.10.0中,版本 1 仍然是默认版本。可以随时在版本 1 和版本 2 之间切换。
可以使用queue-version
策略 键更改版本。使用策略设置新版本时,队列将立即转换其磁盘上的数据。可以升级到版本 2 或降级到版本 1。请注意,对于大型队列,转换可能需要一些时间,并且会导致队列在转换运行时不可用。
可以通过在rabbitmq.conf
中设置classic_queue.default_version
,在配置中设置默认版本。
# makes classic queues use a more efficient message storage
# and queue index implementations
classic_queue.default_version = 2
经典队列 v1 持久化概述
首先,一些背景知识:持久消息和瞬时消息都可以写入磁盘。持久消息在到达队列后会立即写入磁盘,而瞬时消息只会在内存压力下被驱逐出内存时才写入磁盘。持久消息也尽可能地保留在内存中,只有在内存压力下才会被驱逐出内存。“持久化层”是指用于将这两种类型的消息存储到磁盘的机制。
在本页中,我们使用“队列”来指代非复制队列或队列领导者或队列镜像。队列镜像是“高于”持久化的“层”。
持久化层有两个组成部分:队列索引和消息存储。队列索引负责维护有关特定消息在队列中的位置的信息,以及它是否已传递和确认。因此,每个队列都有一个队列索引。
消息存储是每个 vhost 中所有队列共享的消息键值存储。消息(消息体以及任何元数据字段:属性和/或头)可以存储在队列索引中,也可以写入消息存储。从技术上讲,存在两个消息存储(一个用于瞬时消息,一个用于持久消息),但通常将其视为“消息存储”。
内存成本
在内存压力下,持久化层会尝试尽可能多地将数据写入磁盘,并从内存中删除尽可能多的数据。但是,有一些东西必须保留在内存中
- 每个队列都会为每个未确认的消息维护一些元数据。如果消息的目标是消息存储,则可以从内存中删除消息本身。
- 消息存储需要一个索引。默认消息存储索引为存储中的每条消息使用少量内存。
队列索引中的消息嵌入
将消息写入队列索引有优点也有缺点。
此功能有优点也有缺点。主要优点是
- 消息可以一次写入磁盘,而不是两次;对于非常小的消息,这可以节省大量时间。
- 写入队列索引的消息不需要在消息存储索引中创建条目,因此在分页出时没有内存成本。
缺点是
- 队列索引会在内存中保留固定数量记录的块;如果将非常小的消息写入队列索引,则内存使用量可能很大。
- 如果一条消息被交换机路由到多个队列,则需要将消息写入多个队列索引。如果将此类消息写入消息存储,则只需要写入一个副本。
- 目标是队列索引的未确认消息始终保留在内存中。
- 使用版本 2时,仍然需要执行两次写入操作。
目的是将非常小的消息存储在队列索引中作为优化,并将所有其他消息写入消息存储。这由配置项queue_index_embed_msgs_below
控制。默认情况下,序列化大小小于 4096 字节(包括属性和头)的消息存储在队列索引中。
每个队列索引在从磁盘读取消息时都需要在内存中保留至少一个段文件。段文件包含 16,384 条消息的记录。因此,在增加queue_index_embed_msgs_below
时要谨慎;即使是少量增加,也会导致大量内存使用量。
影响持久化的操作系统和运行时限制
持久化可能会由于持久化程序的文件句柄或异步线程数量有限而导致性能下降。在这两种情况下,当有大量队列需要同时访问磁盘时,就会发生这种情况。
文件句柄过少
RabbitMQ 服务器打开的文件句柄数量有限制。每个运行的网络连接都需要一个文件句柄,其余文件句柄可供队列使用。如果在网络连接占用文件句柄后,访问磁盘的队列数量超过了文件句柄数量,则访问磁盘的队列将在彼此之间共享文件句柄;每个队列都可以使用文件句柄一段时间,然后文件句柄会被收回并分配给其他队列。
这可以防止服务器由于访问磁盘的队列过多而崩溃,但这可能变得很昂贵。管理插件可以显示集群中每个节点的 I/O 统计信息;除了显示读取、写入、查找等速率外,它还会显示文件句柄 churn 率——以这种方式回收文件句柄的速率。如果文件句柄过少,繁忙的服务器每秒可能执行数百次重新打开操作——在这种情况下,如果提供更多文件句柄,其性能可能会显着提高。
经典队列 v1:备用消息存储索引实现
如上所述,写入消息存储的每条消息都会使用一小部分内存用于其索引条目。经典队列 v1 使用的消息存储索引在 RabbitMQ 中是可插拔的,其他实现也作为插件提供,可以消除此限制。
它们没有与 RabbitMQ 发行版一起提供的原因是它们都使用原生代码。请注意,此类插件通常会导致消息存储运行速度变慢。