Classic Queues
什么是经典队列
RabbitMQ 经典队列(原始队列类型)是一种通用的队列类型,适用于不优先考虑数据安全性的用例,因为经典队列中存储的数据不会被复制。经典队列使用 **非复制** 的 FIFO 队列实现。
如果数据安全是首要考虑因素,建议使用 仲裁队列 和 流 而非经典队列。
只要未覆盖虚拟主机上默认队列类型,经典队列就是默认的队列类型。
经典队列特性
经典队列完全支持 队列独占性、队列和消息 TTL(生存时间)、队列长度限制、消息优先级、消费者优先级,并遵循 通过策略控制 的设置。
与 仲裁队列 不同,经典队列不支持 毒丸消息处理。经典队列也不支持仲裁队列所支持的至少一次死信。
应优先使用 每个消费者 QoS 预取 而非全局 QoS 预取,尽管经典队列支持这两种选项。全局 QoS 预取是一项已弃用的功能,将在 **RabbitMQ 4.0** 中移除。
虽然经典队列可以声明为瞬态(transient),但这使得在升级过程中节点重启等情况下,队列移除的逻辑变得难以推断,因此不建议使用瞬态队列。瞬态队列的支持已被弃用,并将在 **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(它将重写其磁盘表示)。
请注意,对于大型队列,转换可能需要一些时间,并且在此过程中队列将不可用。作为参考,在我们的测试机器上,迁移需要
- 2 秒迁移 1000 个队列,每个队列包含 1000 条 100 字节的消息
- 9 秒迁移一个包含 100 万条 100 字节消息的队列
- 3 秒迁移一个包含 100 万条 5000 字节消息的队列(使用默认的 4096 字节嵌入大小,5000 字节的消息在消息存储中,因此需要迁移的数据较少)
考虑到以上数字,除非存在大量带有大量消息的队列,否则迁移应在几秒钟内完成。
可以在升级到 RabbitMQ 4.0 之前执行此迁移。有关详细信息,请参阅 RabbitMQ 3.13 文档。
经典队列的资源使用
经典队列旨在提供在大多数情况下无需配置的良好吞吐量。但是,有时进行一些配置会很有用。本节介绍了一些影响节点稳定性、吞吐量、延迟和 I/O 特性的可配置值。考虑熟悉 使用 PerfTest 进行基准测试,以最大限度地利用您的队列。
一些相关信息包括
经典队列的文件句柄使用
RabbitMQ 服务器可以打开的 文件句柄数量 是有限的。每个运行的网络连接都需要一个文件句柄,其余的则可供队列使用。
经典队列版本 2 不再像 v1 那样尝试适应低文件描述符数量。它们期望服务器配置有大的文件描述符限制,并且总能在需要时打开新的文件句柄。索引最多保持 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 分发版一起提供的原因是它们都使用原生代码。请注意,此类插件通常会使消息存储运行得更慢。