跳至主要内容
版本:3.13

经典队列

什么是经典队列

RabbitMQ 经典队列(原始队列类型)是一种多功能的队列类型,适用于数据安全不是优先事项的用例,因为存储在经典队列中的数据不会被复制。经典队列使用**非复制**的 FIFO 队列实现。

如果数据安全是优先事项,建议使用仲裁队列,而不是经典队列。

经典队列是默认的队列类型,除非虚拟主机覆盖了默认的队列类型。

经典队列消息存储和索引有两个版本(实现)。版本仅影响数据在磁盘上的存储和读取方式:所有功能都在两个版本中可用。

经典队列功能

经典队列完全支持队列排他性队列和消息 TTL(生存时间)队列长度限制消息优先级消费者优先级,并遵守使用策略控制的设置.

经典队列支持死信交换机,但至少一次死信队列除外。

经典队列不支持中毒消息处理,与仲裁队列不同。经典队列也不支持至少一次死信队列,仲裁队列支持。

每个消费者 QoS 预取应该优先于全局 QoS 预取,即使经典队列支持这两个选项。全局 QoS 预取是一个已弃用的功能,将在**RabbitMQ 4.0**中删除。

虽然经典队列可以声明为瞬态,但这使得在升级期间节点重启等情况下,队列删除难以推理,因此不建议使用瞬态队列。对瞬态队列的支持已弃用,并将从**RabbitMQ 4.0**中删除。

经典队列可以在集群中的多个节点之间镜像。此功能已弃用,并将从**RabbitMQ 4.0**中删除。仲裁队列在需要高可用性和数据安全时提供更好的替代方案。

直到**RabbitMQ 3.12**,经典队列可以在**延迟模式**下运行。从 RabbitMQ 3.12 开始,队列模式被忽略,经典队列的行为类似于延迟队列。它们只在内存中保留少量消息,具体取决于消费率;所有其他消息都直接写入磁盘。

经典队列中的持久性(持久存储)

经典队列使用磁盘上的索引来存储消息在磁盘上的位置,以及使用消息存储来持久化消息。

两种持久性和瞬态消息始终被持久化到磁盘,除非

  • 队列被声明为瞬态或消息是瞬态的
  • 消息大小小于嵌入阈值(默认为 4096 字节)
  • 对于**RabbitMQ 3.12**及更高版本:队列很短(队列最多可以在内存中保留 2048 条消息,具体取决于消费者传送率)

通常,消息不会保存在内存中,除非消息的消费率足够高,以至于预计内存中的消息将在下一秒内被消费。经典队列在内存中保留最多 2048 条消息,具体取决于消费者传送率。较大的消息不会读入内存,直到它们必须发送给消费者时。

持久化消息可以**嵌入**到队列中,也可以发送到**共享消息存储**中。将消息存储在队列中还是共享消息存储中的决定是根据消息的大小(包括头)做出的。共享消息存储在处理较大的消息方面更有效,特别是当这些消息发送到多个队列时。

消息位置写入队列的索引中。每个队列都有一个索引。队列负责跟踪消息的位置及其在队列中的位置,并将其信息持久化到索引中。

嵌入式消息在使用经典队列版本 1 时写入其队列索引;在使用经典队列版本 2 时写入其**每个队列的消息存储**。

较大的消息写入共享消息存储中。每个 vhost 有两个这样的存储:一个用于持久化消息,一个用于瞬态消息,但它们通常被认为是共享消息存储。vhost 中的所有队列都使用相同的消息存储。

经典队列存储实现版本

目前有两种经典队列版本(实现)。根据版本,经典队列将使用不同的消息索引,以及在索引中嵌入小消息方面也表现不同。

经典队列实现版本 1

版本 1 是默认版本,也是经典队列的原始实现。版本 1 中的索引使用日志文件和段文件的组合。从段文件读取消息时,它会将整个段文件加载到内存中,这会导致内存问题,但可以减少执行的 I/O 量。版本 1 将小消息嵌入到索引中,进一步加剧内存问题。

经典队列实现版本 2

版本 2 利用了现代存储设备的性能改进,是推荐的版本。版本 2 中的索引只使用段文件,并且只在必要时从磁盘加载消息。它将根据当前消费率加载更多消息。版本 2 不会将其消息嵌入到索引中,而是使用每个队列的消息存储。

版本 2 添加在**RabbitMQ 3.10.0**中,并在**RabbitMQ 3.12.0**中得到了显著改进。目前可以在这两个版本之间切换。将来,版本 1 将被删除,并且在升级后节点启动时会自动执行迁移到版本 2。

可以使用queue-version策略键更改版本。通过策略设置新版本时,队列将立即转换其磁盘上的数据。可以升级到版本 2 或降级到版本 1。请注意,对于大型队列,转换可能需要一些时间,并且会导致队列在转换运行期间不可用。作为参考,在我们的测试机器上,迁移需要

  • 2 秒迁移 1000 个队列,每个队列包含 1000 条 100 字节的消息
  • 9 秒迁移包含 100 万条 100 字节的消息的队列
  • 3 秒迁移包含 100 万条 5000 字节的消息的队列(使用默认的 4096 字节嵌入大小,5000 字节的消息在消息存储中,因此迁移的数据更少)

鉴于以上数字,除非有大量队列包含大量消息,否则迁移应该在几秒钟内完成。

可以通过在 rabbitmq.conf 中设置classic_queue.default_version来通过配置设置默认版本。更改默认版本只影响新声明的队列。预先存在的队列将保留在版本 1 上,直到显式迁移或删除并重新声明。

经典队列的资源使用

经典队列旨在在大多数情况下提供合理良好的吞吐量,无需配置。但是,有时一些配置很有用。本节介绍几个可配置的值,这些值会影响节点的稳定性、吞吐量、延迟和 I/O 特性。除了使用 PerfTest 进行基准测试以充分利用您的队列之外,请考虑熟悉这些内容。

一些相关信息包括

经典队列的文件句柄使用

RabbitMQ 服务器在可以打开的文件句柄数量方面受到限制。每个运行的网络连接都需要一个文件句柄,其余的文件句柄可供队列使用。

经典队列在文件句柄使用方面表现不同,具体取决于使用的版本。

版本 1 试图避免出现文件描述符不足的情况。如果在考虑网络连接后,磁盘访问队列的数量大于文件句柄,那么磁盘访问队列将共享文件句柄;每个队列都可以使用一段时间的文件句柄,然后再被收回并提供给另一个队列。

这可以防止服务器由于磁盘访问队列过多而崩溃,但它会变得很昂贵。管理插件可以显示集群中每个节点的 I/O 统计信息;除了显示读、写、查找的速率外,它还会显示文件句柄 churn 的速率——文件句柄以这种方式循环利用的速率。如果一台繁忙的服务器的文件句柄太少,每秒可能会有数百次重新打开操作——在这种情况下,如果提供更多文件句柄,其性能可能会明显提高。

版本 2 不再尝试适应少量文件描述符。它期望服务器配置了较大的文件描述符限制,并且能够在需要时始终打开新的文件句柄。索引最多会同时打开 4 个文件句柄,而每个队列的存储会打开 1 个文件句柄,但在将数据刷新到磁盘时可能会打开另一个文件句柄。这意味着每个队列理论上需要最多 6 个文件描述符才能正常工作。实际上,只有繁忙的队列才需要这么多文件描述符;其他队列使用 3 或 4 个文件句柄就可以正常工作。

由于没有使用文件句柄管理子系统,版本 2 不跟踪许多 I/O 统计信息;只跟踪读取和写入次数。其他指标可以在操作系统级别获取。

经典队列的内存占用

经典队列最多可以在内存中保留 2048 条消息,具体取决于消费速度。但是,经典队列会避免过早地从磁盘读取较大的消息。在 **RabbitMQ 3.12** 中,这意味着大于嵌入式阈值(默认情况下为 4096 字节)的消息。

版本 1 中的索引必须读取整个段文件才能访问其中的消息。这会导致内存使用量激增,尤其是在嵌入大量消息时。这也会导致 CPU 使用率激增,因为垃圾回收次数增加。此外,索引会缓冲写入并将数据保留在内存中,直到达到 1MB 后才会刷新到磁盘。

版本 2 中的索引和每个队列的存储会缓冲条目。这通常不是索引的关注点,因为它只跟踪元数据。但是,每个队列的存储默认会使用最多 1MB 的内存(写入缓冲区中 512KB 和缓存中 512KB)。在刷新到磁盘时,存储首先会清除缓存,然后将写入缓冲区中的消息移动到缓存,有效地用写入缓冲区中的数据替换缓存中的数据。因此,写入缓冲区的大小和缓存的大小是关联的。可以使用高级配置通过 Rabbit 的 `classic_queue_store_v2_max_cache_size` 参数对其进行配置。

空闲队列会减少内存使用量。当执行影响许多队列的操作(例如定义新的策略)时,这有时会导致意外的激增。在这种情况下,队列将需要再次分配更多内存。队列越多,预计的激增越大。

共享消息存储需要索引。默认的消息存储索引使用少量内存来存储每个消息的索引条目。

替代消息存储索引实现

如上所述,写入消息存储的每条消息都会使用少量内存来存储其索引条目。消息存储索引在 RabbitMQ 中是可插拔的,其他实现可以作为插件提供,可以消除此限制。

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