跳至主内容
版本:4.2

持久化配置

概述

本指南涵盖了几个影响节点吞吐量、延迟和 I/O 特性的可配置值。在得出任何结论之前,请考虑阅读整个指南并熟悉使用 PerfTest 进行基准测试

一些相关指南包括

RabbitMQ 持久化概述

现代 RabbitMQ 版本提供了多种队列类型和流(streams)

  • 仲裁队列(Quorum queues):复制的、持久化的、数据安全导向的
  • 流(Streams):一种复制的、持久化的数据结构,支持与队列不同的操作
  • 经典队列(Classic queues):原始队列类型,RabbitMQ 4.0 起仅支持单副本

这些队列类型具有不同的存储实现,适用的配置设置也不同。

流使用基于日志的存储机制,内存中只保留少量数据(主要是尚未写入磁盘的运行数据)。尽管如此,当客户端使用RabbitMQ 流协议时,它们仍能提供出色的吞吐量。

由于流在磁盘 I/O 方面非常消耗资源,它们的吞吐量会随着消息大小的增大而降低。它们非常受益于现代 SSD 和 NVMe 存储。

流不提供与存储相关的可调参数。

仲裁队列

仲裁队列使用 RabbitMQ 的 Raft 实现所实现的基于日志的存储机制。内存中只保留少量数据(主要是尚未写入磁盘的运行数据)。

由于仲裁队列在执行任何操作之前都会将所有数据持久化到磁盘,因此建议使用最快的磁盘。

由于仲裁队列的磁盘 I/O 密集型特性,它们的吞吐量会随着消息大小的增加而下降。

影响仲裁队列资源使用的主要存储相关设置是预写日志(write-ahead log)段大小限制,即 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 中的所有队列之间共享。消息(正文和任何元数据字段:属性和/或头部)可以存储在队列索引中,也可以写入消息存储。技术上有两个消息存储(一个用于瞬态消息,一个用于持久化消息),但它们通常被视为一个整体——“消息存储”。

内存成本

在内存压力下,持久化层会尝试将尽可能多的数据写入磁盘,并从内存中移除尽可能多的数据。但有些东西必须保留在内存中

  • 每个队列都会为每条未确认的消息维护一些元数据。如果消息的目的地是消息存储,则消息本身可以从内存中移除。
  • 消息存储需要一个索引。默认的消息存储索引会为存储中的每条消息占用少量内存。

消息嵌入到队列索引中

将消息写入队列索引有利有弊。

此功能有优点和缺点。主要优点是

  • 消息可以一次性写入磁盘,而不是两次;对于非常小的消息,这可以带来显著的性能提升。
  • 写入队列索引的消息不需要在消息存储索引中条目,因此在分页(paged out)时没有内存成本。

缺点是

  • 队列索引在内存中保留固定数量记录的块;如果非微小消息写入队列索引,内存使用量会很大。
  • 如果一条消息被路由到交换机的多个队列,该消息需要写入多个队列索引。如果这样的消息写入消息存储,只需写入一个副本。
  • 目的地为队列索引的未确认消息始终保留在内存中。
  • 使用版本 2 时,仍需要两次写入。

目的是将非常小的消息作为优化手段存储在队列索引中,而将所有其他消息写入消息存储。这由配置项 `queue_index_embed_msgs_below` 控制。默认情况下,序列化大小小于 4096 字节(包括属性和头部)的消息存储在队列索引中。

每个队列索引在从磁盘读取消息时,都需要在内存中至少保留一个段文件。段文件包含 16,384 条消息的记录。因此,如果您增加 `queue_index_embed_msgs_below`,请谨慎;即使是小的增加也可能导致大量的内存使用。

影响持久化的操作系统和运行时限制

持久化可能会表现不佳,因为持久化程序在文件句柄数量或异步线程数量方面受到限制。在两种情况下,当您有大量队列需要同时访问磁盘时,都可能发生这种情况。

文件句柄太少

RabbitMQ 服务器可打开的文件句柄数量有限。每个正在运行的网络连接都需要一个文件句柄,其余的供队列使用。如果扣除网络连接后,需要访问磁盘的队列多于可用的文件句柄,那么访问磁盘的队列将共享这些文件句柄;每个队列在一段时间内使用一个文件句柄,然后该文件句柄会被收回并分配给另一个队列。

这可以防止服务器因过多的磁盘访问队列而崩溃,但可能会变得昂贵。管理插件可以显示集群中每个节点的 I/O 统计信息;除了显示读、写、寻道等速率外,它还会显示文件句柄的周转率(churn rate)——即文件句柄被这样回收的速率。一个文件句柄太少而繁忙的服务器可能每秒会发生数百次重新打开操作——在这种情况下,如果提供更多文件句柄,其性能可能会显著提高。

经典队列 v1:替代消息存储索引实现

如上所述,每条写入消息存储的消息在其索引条目中都会占用少量内存。经典队列 v1 使用的消息存储索引是可插拔的,并且有其他实现作为插件可用,这些插件可以消除此限制。

它们没有随 RabbitMQ 发行版一起提供的原因是它们都使用原生代码。请注意,此类插件通常会使消息存储运行速度变慢。

© . This site is unofficial and not affiliated with VMware.