Classic Queues
什么是经典队列
RabbitMQ 经典队列(原始队列类型)是一种通用的队列类型,适用于对数据安全性要求不高的场景,因为存储在经典队列中的数据不会被复制。经典队列使用非复制的 FIFO(先进先出)队列实现。
如果数据安全性是优先考虑事项,建议使用 仲裁队列 (quorum queues) 和 流 (streams),而不是经典队列。
只要未针对虚拟主机 (vhost) 覆盖默认队列类型,经典队列即为默认队列类型。
经典队列功能
经典队列完全支持 队列排他性、队列和消息 TTL(生存时间)、队列长度限制、消息优先级、消费者优先级,并遵循由 策略控制 的设置。
经典队列支持 死信交换机,但不支持 至少一次投递的死信机制。
与 仲裁队列 不同,经典队列不支持 毒丸消息处理。经典队列也不支持仲裁队列所具备的至少一次投递的死信机制。
尽管经典队列同时支持两种选项,但应优先选择 消费者级 QoS 预取 而非全局 QoS 预取。全局 QoS 预取是一项已弃用的功能,将在 RabbitMQ 4.0 中移除。
虽然经典队列可以声明为瞬态队列,但这会导致在升级等节点重启期间难以确定队列的移除逻辑,因此不鼓励使用瞬态队列。对瞬态队列的支持已被弃用,并将在 RabbitMQ 4.0 中移除。
自 RabbitMQ 4.0 起,经典队列是一种非复制队列类型。当需要高可用性和数据安全性时,仲裁队列 和 流 提供了更好的选择。
经典队列中的持久化(持久化存储)
经典队列使用磁盘索引来存储磁盘上的消息位置,并使用消息存储来持久化消息。
除非满足以下条件,否则持久化消息和瞬态消息通常都会被持久化到磁盘:
- 队列被声明为瞬态,或者消息本身是瞬态的
- 消息大小小于嵌入阈值(默认为 4096 字节)
- 队列较短(根据消费者投递速率,队列在内存中最多可保留 2048 条消息)
通常情况下,消息不会一直保存在内存中,除非消息的消费速率很高,使得内存中的消息预计能在下一秒内被消费掉。经典队列根据消费者投递速率在内存中保留最多 2048 条消息。较大的消息在需要发送给消费者之前,不会被读入内存。
持久化消息可以嵌入到队列索引中,或者发送到共享消息存储中。将消息存储在队列中还是共享消息存储中的决定取决于消息的大小(包括标头)。共享消息存储在处理较大消息时效率更高,特别是当这些消息被发送到多个队列时。
消息的位置记录在队列的索引中。每个队列都有一个索引。队列负责跟踪消息的位置及其在队列中的排序,并将此信息持久化在索引中。
使用经典队列版本 1 时,嵌入式消息被写入队列索引;使用经典队列版本 2 时,嵌入式消息被写入其队列专属消息存储中。
较大的消息会被写入共享消息存储。每个 vhost 都有两个此类存储:一个用于持久化消息,一个用于瞬态消息,但它们通常被统称为共享消息存储。vhost 中的所有队列都使用相同的消息存储。
经典队列存储实现版本
目前有两种经典队列版本(实现)。根据版本的不同,经典队列将使用不同的索引来存储消息,并且在索引中嵌入小消息的操作方式也不同。
经典队列实现版本 1
RabbitMQ 4.0 已移除了对经典队列版本 1 的支持。
经典队列实现版本 2
版本 2 中的索引仅使用分段文件,并且仅在必要时才从磁盘加载消息。它会根据当前的消费速率加载更多消息。版本 2 不会在索引中嵌入消息,而是使用每个队列专属的消息存储。
版本 2 在 RabbitMQ 3.10.0 中引入,并在 RabbitMQ 3.12.0 中得到了显著改进。自 RabbitMQ 4.0 起,不再支持版本 1 队列。
版本 1 到版本 2 的迁移
当 RabbitMQ 4.0 节点启动时,它会自动将任何现有的 v1 队列迁移到 v2(它会重写它们在磁盘上的表示形式)。
请注意,对于大型队列,转换可能需要一些时间,并且在转换过程中队列将不可用。作为参考,在我们的测试机器上,迁移所需时间如下:
- 迁移 1000 个队列(每个队列有 1000 条 100 字节的消息)需要 2 秒
- 迁移一个包含 100 万条 100 字节消息的队列需要 9 秒
- 迁移一个包含 100 万条 5000 字节消息的队列需要 3 秒(默认嵌入大小为 4096 字节,因此 5000 字节的消息存储在消息存储中,需要迁移的数据较少)
根据上述数据,除非存在大量包含大量消息的队列,否则迁移应该会在几秒钟内完成。
可以在升级到 RabbitMQ 4.0 之前执行此迁移。有关详细信息,请参阅 RabbitMQ 3.13 文档。
经典队列的资源使用
经典队列旨在在大多数情况下无需配置即可提供相当不错的吞吐量。不过,某些配置有时非常有用。本节涵盖了一些影响节点稳定性、吞吐量、延迟和 I/O 特性的可配置值。除了这些配置外,还建议熟悉 使用 PerfTest 进行基准测试,以充分发挥队列的性能。
一些相关信息包括:
经典队列的文件句柄使用
RabbitMQ 服务器可以打开的 文件句柄数量 是有限的。每个正在运行的网络连接都需要一个文件句柄,其余的供队列使用。
经典队列版本 2 不再像版本 1 那样尝试适应较少的文件描述符。它们要求服务器配置较大的文件描述符限制,并能够在必要时始终打开新的文件句柄。索引在任何时候最多保持 4 个文件句柄处于打开状态,队列专属存储保持 1 个文件句柄处于打开状态,但在将数据刷新到磁盘时可能会打开另一个。这意味着从理论上讲,每个队列需要 6 个可用的文件描述符才能正常工作。实际上,只有繁忙的队列才会需要那么多;其他队列使用 3 或 4 个文件句柄即可正常工作。
由于不使用文件句柄管理子系统,版本 2 不会跟踪那么多的 I/O 统计信息;仅记录读写次数。其他指标可以在操作系统层面获取。
经典队列的内存占用
经典队列根据消费速率在内存中最多保留 2048 条消息。但是,经典队列会避免过早从磁盘读取较大的消息。在 RabbitMQ 3.12 中,这意味着大于嵌入阈值(默认为 4096 字节)的消息不会被过早读取。
版本 2 中的索引和队列专属存储会缓冲条目。就索引而言,这通常不是问题,因为它只跟踪元数据。但是,每个队列的存储默认情况下会使用高达 1MB 的内存(写缓冲区中 512KB,缓存中 512KB)。刷新到磁盘时,存储会先清除缓存,然后将写缓冲区中的消息移动到缓存,有效地用写缓冲区中的数据替换缓存中的数据。因此,写缓冲区和缓存的大小是关联的。可以通过高级配置使用 rabbit 的 classic_queue_store_v2_max_cache_size 参数进行配置。
空闲队列会降低内存使用量。在执行影响许多队列的操作(例如定义新策略)时,这有时会导致意外的内存占用激增。在这种情况下,队列将需要再次分配更多内存。队列越多,预期的激增就越大。
共享消息存储需要一个索引。默认的消息存储索引为存储中的每条消息使用少量内存。
替代消息存储索引实现
如上所述,写入消息存储的每条消息都会为其索引条目使用少量内存。RabbitMQ 中的消息存储索引是可插拔的,其他实现作为插件提供,可以消除此限制。
它们未包含在 RabbitMQ 发行版中的原因是它们都使用了原生代码。请注意,此类插件通常会使消息存储运行得更慢。