仲裁队列
概述
RabbitMQ 仲裁队列是一种现代队列类型,它基于 Raft 共识算法 实现持久、复制的队列,在需要复制、高可用的队列时,应将其视为默认选择。
仲裁队列专为卓越的数据安全以及可靠且快速的领导者选举属性而设计,以确保即使在升级或其他动荡期间也能实现高可用性。
仲裁队列针对某些 用例 进行了优化,在这些用例中,数据安全 是首要优先级。这在 动机 部分中进行了介绍。
仲裁队列与经典队列相比,在 行为上有所不同,并且存在一些 限制,在将应用程序从使用经典队列转换为仲裁队列时,务必注意这些差异和限制。
某些功能是仲裁队列特有的,例如 毒消息处理、至少一次死信 以及使用 AMQP 时的 modified
结果。
对于那些可以从复制和可重复读取中受益的场景,流 可能是比仲裁队列更好的选择。
仲裁队列和 流 是两种可用的复制数据结构。经典队列镜像已从 RabbitMQ 4.0 开始移除。
涵盖主题
本文档涵盖的主题包括
- 什么是仲裁队列 以及引入它们的原因
- 它们与经典队列有何不同
- 仲裁队列的主要 用例 以及何时不应使用它们
- 如何 声明仲裁队列
- 复制 相关主题:副本管理、副本领导者重新平衡、最佳副本数量等
- 仲裁队列在 领导者故障处理、数据安全 和 可用性 方面提供的保证
- 持续的 成员资格协调
- 仲裁队列支持的额外 死信 功能
- 仲裁队列的内存和磁盘占用
- 仲裁队列的性能 特征
- 性能调优,包括针对 小消息 和 大消息 的工作负载
- 毒消息处理(故障-重新交付循环保护)
- 放宽 属性等价性 的选项
- 仲裁队列的可配置设置
等等。
在了解更多关于仲裁队列的信息时,熟悉 RabbitMQ 集群 会有所帮助。
动机
仲裁队列采用不同的复制和共识协议,并放弃对某些本质上“瞬态”的功能的支持,这导致了一些限制。这些限制将在本文档后面介绍。
仲裁队列通过了 重构且要求更高的版本 的 原始 Jepsen 测试。这确保了它们在网络分区和故障场景下表现符合预期。新的测试持续运行,以发现可能的回归,并定期增强以测试新功能(例如 死信)。
什么是仲裁?
如果故意简化,分布式系统中的 仲裁 可以定义为大多数节点之间的协议((N/2)+1
,其中 N
是系统参与者的总数)。
当应用于 RabbitMQ 集群 中的队列镜像时,这意味着大多数副本(包括当前选出的队列领导者)就队列的状态及其内容达成一致。
用例
仲裁队列的预期用途是在拓扑中,队列长期存在,并且对于应用程序架构的某些方面至关重要。
良好的用例示例包括销售系统中的传入订单或选举系统中的投票,在这些系统中,潜在的消息丢失会对整体系统的正确性和功能产生重大影响。
仲裁队列并非旨在用于解决所有问题。股票行情、即时消息系统和 RPC 回复队列从使用仲裁队列中获益较少或根本没有获益。
发布者应 使用发布者确认,因为这是客户端与仲裁队列共识系统交互的方式。一旦已发布的消息已成功复制到仲裁副本,并且在队列的上下文中被认为是“安全的”,发布者确认才会 发出。
消费者应使用 手动确认,以确保未成功处理的消息返回到队列,以便另一个消费者可以重新尝试处理。
何时不应使用仲裁队列
在某些情况下,不应使用仲裁队列。它们通常涉及
- 临时队列:瞬态或独占队列、高队列 churn(声明和删除率)
- 最低可能的延迟:由于其数据安全功能,底层共识算法具有固有的更高延迟
- 当数据安全不是首要任务时(例如,应用程序不使用 手动确认,并且不使用发布者确认)
- 非常长的队列积压(5M+ 消息)(流 可能是更合适的选择)
- 大型扇出:(流 可能是更合适的选择)
功能
仲裁队列与其他 队列 类型共享大多数基本原理。
与经典队列的比较
以下操作对于仲裁队列和经典队列的工作方式相同
在某些队列操作中,存在细微差异
- 声明
- 为消费者设置预取
功能矩阵
功能 | 经典队列 | 仲裁队列 |
---|---|---|
非持久队列 | 是 | 否 |
消息复制 | 否 | 是 |
独占性 | 是 | 否 |
每消息持久性 | 每消息 | 总是 |
成员资格更改 | 否 | 半自动 |
消息 TTL(生存时间) | 是 | 是 |
队列 TTL | 是 | 部分(租约不会在队列重新声明时续订) |
队列长度限制 | 是 | 是(x-overflow :reject-publish-dlx 除外) |
将消息保留在内存中 | 请参阅 经典队列 | 从不(请参阅 资源使用) |
消息优先级 | 是 | 是 |
单个活动消费者 | 是 | 是 |
消费者独占性 | 是 | 否(使用 单个活动消费者) |
消费者优先级 | 是 | 是 |
死信交换机 | 是 | 是 |
遵守 策略 | 是 | 是(请参阅 策略支持) |
毒消息处理 | 否 | 是 |
服务器命名队列 | 是 | 否 |
现代仲裁队列还为许多工作负载提供了 更高的吞吐量和更低的延迟可变性。
队列和每消息 TTL
仲裁队列支持 队列 TTL 和消息 TTL(自 RabbitMQ 3.10 起)(包括 队列中的每队列消息 TTL 和 发布者中的每消息 TTL)。当使用任何形式的消息 TTL 时,每个消息的内存开销增加 2 个字节。
长度限制
仲裁队列支持 队列长度限制。
支持 drop-head
和 reject-publish
溢出行为,但它们不支持 reject-publish-dlx
配置,因为仲裁队列采用与经典队列不同的实现方法。
当前 reject-publish
溢出行为的实现并不能严格执行限制,并允许仲裁队列超出其限制至少一条消息,因此在需要精确限制的场景中应谨慎使用。
当仲裁队列达到最大长度限制并且配置了 reject-publish
时,它会通知每个发布通道,此后,这些通道将拒绝返回客户端的所有消息。这意味着仲裁队列可能会超出其限制少量消息,因为在通知通道时可能存在正在传输中的消息。队列接受的额外消息数量将取决于当时正在传输中的消息数量。
死信队列
仲裁队列支持 通过死信交换机 (DLX) 进行死信处理。
传统上,在集群环境中使用 DLX 并未 安全。
仲裁队列支持更安全的死信形式,该形式对队列之间的消息传输使用 至少一次
保证(具有以下概述的限制和注意事项)。
这通过实现一个特殊的内部死信消费者进程来完成,该进程的工作方式类似于具有手动确认的普通队列消费者,不同之处在于它仅消费已死信的消息。
这意味着源仲裁队列将保留死信消息,直到它们被确认为止。内部消费者将消费死信消息,并使用发布者确认将其发布到目标队列。它仅在收到发布者确认后才会确认,因此提供 至少一次
保证。
至多一次
仍然是仲裁队列的默认死信策略,并且适用于死信消息更多的是信息性质,并且如果它们在队列之间传输时丢失,或者在不适合下面概述的溢出配置限制的情况下,无关紧要的场景。
激活至少一次死信
要为源仲裁队列激活或启用 至少一次
死信,请调整 启用死信的策略,如下所示
- 将
dead-letter-strategy
设置为at-least-once
(默认为at-most-once
)。 - 将
overflow
设置为reject-publish
(默认为drop-head
)。 - 配置
dead-letter-exchange
- 如果未启用,请启用
stream_queue
功能标志
建议额外配置 max-length
或 max-length-bytes
以防止源仲裁队列中消息过度堆积(请参阅下面的注意事项)。
可选地,配置 dead-letter-routing-key
。
限制
至少一次
死信处理不适用于默认的 drop-head
溢出策略,即使未设置队列长度限制也是如此。因此,如果配置了 drop-head
,死信将回退到 至多一次
。请改用溢出策略 reject-publish
。
注意事项
至少一次
死信处理将需要更多系统资源,例如内存和 CPU。因此,仅当不应丢失死信消息时才启用 至少一次
。
至少一次
保证会带来一些特定的故障情况,需要处理。由于死信消息现在由源仲裁队列保留,直到它们被死信目标队列安全接受为止,这意味着它们必须计入队列资源限制,例如最大长度限制,以便队列可以拒绝接受更多消息,直到删除一些消息为止。从理论上讲,队列有可能只包含死信消息,例如,在目标死信队列长时间无法接受消息并且普通队列消费者消费了大部分消息的情况下。
死信消息在被死信目标队列确认之前,都被视为“活动”消息。
在少数情况下,死信消息不会及时从源队列中删除
- 配置的死信交换机不存在。
- 消息无法路由到任何队列(等效于
mandatory
消息属性)。 - 一个(可能是多个中的一个)路由的目标队列未确认收到消息。当目标队列不可用或目标队列拒绝消息时(例如,由于超出队列长度限制),可能会发生这种情况。
如果发生上述任何一种情况,死信消费者进程将定期重试,这意味着重复项可能会出现在 DLX 目标队列中。
对于每个启用了 至少一次
死信的仲裁队列,将有一个内部死信消费者进程。内部死信消费者进程与仲裁队列领导者节点位于同一位置。它将所有死信消息体保存在内存中。如果未从目标队列收到确认,它将使用 32 条消息的预取大小来限制保存在内存中的消息体数量。
如果需要高死信吞吐量(每秒数千条消息),则可以通过 高级配置文件 的 rabbit
应用程序部分中的 dead_letter_worker_consumer_prefetch
设置来增加该预取大小。
对于源仲裁队列,可以动态地将死信策略从 至多一次
切换到 至少一次
,反之亦然。如果死信策略直接从 至少一次
更改为 至多一次
,或者间接更改,例如通过将溢出从 reject-publish
更改为 drop-head
,则任何尚未被所有目标队列确认的死信消息都将被删除。
发布到源仲裁队列的消息将持久化到磁盘,而与消息传递模式(瞬态或持久)无关。但是,由源仲裁队列死信的消息将保留原始消息传递模式。这意味着,如果目标队列中的死信消息应在代理重新启动后仍然存在,则目标队列必须是持久的,并且在将消息发布到源仲裁队列时,消息传递模式必须设置为持久。
优先级
仲裁队列优先级支持自 RabbitMQ 4.0 起可用。但是,仲裁队列和经典队列实现优先级的方式存在差异。
仲裁队列支持 消费者优先级,并且从 4.0 开始,它们还支持一种消息优先级类型,该类型与 经典队列消息优先级 截然不同。
仲裁队列消息优先级始终处于活动状态,不需要策略即可工作。一旦仲裁队列收到设置了优先级的消息,它将启用优先级排序。
仲裁队列内部仅支持两种优先级:高优先级和普通优先级。未设置优先级的消息将映射到普通优先级,优先级 0 - 4 也将映射到普通优先级。优先级高于 4 的消息将映射到高优先级。
高优先级消息将以 2:1 的比例优先于普通优先级消息,即,对于每 2 条高优先级消息,队列将交付 1 条普通优先级消息(如果可用)。因此,仲裁队列实现了一种非严格的“公平共享”优先级处理。这确保了始终在普通优先级消息上取得进展,但高优先级以 2:1 的比例优先。
如果高优先级消息在普通优先级消息之前发布,则始终首先交付高优先级消息,即使轮到普通优先级也是如此。
更高级的场景
对于更高级的消息优先级场景,应为不同的消息类型使用单独的队列,每种类型一个队列(优先级)。用于更重要消息类型的队列通常应具有更多(过度配置的)消费者。
毒消息处理
仲裁队列支持处理 毒消息,即导致消费者重复重新排队交付的消息(可能是由于消费者故障),从而导致消息永远无法完全消费,并且 肯定确认,以便 RabbitMQ 可以将其标记为删除。
仲裁队列跟踪不成功(重新)交付尝试的次数,并在包含在任何重新交付消息中的“x-delivery-count”标头中公开它。
建议所有仲裁队列都具有某种死信配置,以确保消息不会被意外丢弃和丢失。使用单个 流 来实现低优先级死信策略是一种很好的低资源方式,可确保丢弃的消息在一段时间后仍被保留。
从 RabbitMQ 4.0 开始,仲裁队列的交付限制默认为 20。
可以通过在声明时使用 可选队列参数 或使用如下所示的策略将 3.13.x 时代的无限制行为恢复。
有关更多详细信息,请参阅 重复重新排队。
配置限制
可以使用 策略 参数 delivery-limit
为队列设置交付限制。
覆盖限制
以下示例将名称以 qq
开头的队列的限制设置为 50。
- bash
- PowerShell
- HTTP API
- 管理 UI
rabbitmqctl set_policy qq-overrides \
"^qq\." '{"delivery-limit": 50}' \
--priority 123 \
--apply-to "quorum_queues"
rabbitmqctl.bat set_policy qq-overrides ^
"^qq\." "{""delivery-limit"": 50}" ^
--priority 123 ^
--apply-to "quorum_queues"
PUT /api/policies/%2f/qq-overrides
{"pattern": "^qq\.",
"definition": {"delivery-limit": 50},
"priority": 1,
"apply-to": "quorum_queues"}
导航到
Admin
>Policies
>添加/更新策略
。在“名称”旁边输入策略名称(例如“qq-overrides”),在“模式”旁边输入模式(例如“^qq.”),并使用“应用于”下拉菜单选择策略应应用于的实体类型(在本例中为仲裁队列)。
在“策略”旁边的第一行中输入策略参数“delivery-limit”,并为其值输入 50。
单击
添加策略
。
禁用限制
以下示例禁用名称以 qq.unlimited
开头的队列的限制。
- bash
- PowerShell
- HTTP API
- 管理 UI
rabbitmqctl set_policy qq-overrides \
"^qq\.unlimited" '{"delivery-limit": -1}' \
--priority 123 \
--apply-to "quorum_queues"
rabbitmqctl.bat set_policy qq-overrides ^
"^qq\.unlimited" "{""delivery-limit"": -1}" ^
--priority 123 ^
--apply-to "quorum_queues"
PUT /api/policies/%2f/qq-overrides
{"pattern": "^qq\.unlimited",
"definition": {"delivery-limit": -1},
"priority": 1,
"apply-to": "quorum_queues"}
导航到
Admin
>Policies
>添加/更新策略
。在“名称”旁边输入策略名称(例如“qq-overrides”),在“模式”旁边输入模式(例如“^qq.unlimited”),并使用“应用于”下拉菜单选择策略应应用于的实体类型(在本例中为仲裁队列)。
在“策略”旁边的第一行中输入策略参数“delivery-limit”,并为其值输入 -1。
单击
添加策略
。
配置限制和设置死信队列
重新交付次数超过限制的消息将被丢弃(删除)或 死信。
以下示例配置了限制和一个交换机以死信(重新发布)此类消息。本示例中的目标交换机名为“redeliveries.limit.dlx”。声明它并设置其拓扑(将队列和/或流绑定到它)不在本示例中介绍。
- bash
- PowerShell
- HTTP API
- 管理 UI
rabbitmqctl set_policy qq-overrides \
"^qq\." '{"delivery-limit": 50, "dead-letter-exchange": "redeliveries.limit.dlx"}' \
--priority 123 \
--apply-to "quorum_queues"
rabbitmqctl.bat set_policy qq-overrides ^
"^qq\." "{""delivery-limit"": 50, ""dead-letter-exchange"": ""redeliveries.limit.dlx""}" ^
--priority 123 ^
--apply-to "quorum_queues"
PUT /api/policies/%2f/qq-overrides
{"pattern": "^qq\.",
"definition": {"delivery-limit": 50, "dead-letter-exchange": "redeliveries.limit.dlx"},
"priority": 1,
"apply-to": "quorum_queues"}
导航到
Admin
>Policies
>添加/更新策略
。在“名称”旁边输入策略名称(例如“qq-overrides”),在“模式”旁边输入模式(例如“^qq.”),并使用“应用于”下拉菜单选择策略应应用于的实体类型(在本例中为仲裁队列)。
在“策略”旁边的第一行中输入策略参数“delivery-limit”,并为其值输入 50,然后在第二个键中输入“dead-letter-exchange”,并为其值输入“redeliveries.limit.dlx”。
单击
添加策略
。
要了解有关死信的更多信息,请查阅其 专用指南。
策略支持
仲裁队列可以通过 RabbitMQ 策略进行配置。下表总结了它们遵守的策略键。
定义键 | 类型 |
---|---|
max-length | 数字 |
max-length-bytes | 数字 |
overflow | “drop-head”或“reject-publish” |
expires | 数字(毫秒) |
dead-letter-exchange | 字符串 |
dead-letter-routing-key | 字符串 |
delivery-limit | 数字 |
不支持的功能
瞬态(非持久)队列
经典队列可以是 非持久的。根据其假定的 用例,仲裁队列始终是持久的。
独占性
独占队列 与其声明连接的生命周期相关联。仲裁队列在设计上是复制和持久的,因此独占属性在它们的上下文中没有意义。因此,仲裁队列不能是独占的。
仲裁队列不适合用作 临时队列。
全局 QoS
仲裁队列不支持全局 QoS 预取,其中通道为使用该通道的所有消费者设置单个预取限制。如果尝试从激活了全局 QoS 的通道消费仲裁队列,则将返回通道错误。
使用 每消费者 QoS 预取,这是几种流行的客户端中的默认设置。
用法
仲裁队列与其他 队列 类型共享大多数基本原理。任何可以在声明时指定 可选队列参数 的 AMQP 0.9.1 客户端库都能够使用仲裁队列。
首先,我们将介绍如何使用 AMQP 0.9.1 声明仲裁队列。
声明
要声明仲裁队列,请将 x-queue-type
队列参数设置为 quorum
(默认为 classic
)。此参数必须由客户端在队列声明时提供;不能使用 策略 设置或更改它。这是因为策略定义或适用的策略可以动态更改,但队列类型不能。它必须在声明时指定。
使用设置为 quorum
的 x-queue-type
参数声明队列将声明一个最多具有三个副本(默认 复制因子)的仲裁队列,每个 集群节点 一个副本。
例如,一个包含三个节点的集群将具有三个副本,每个节点上一个副本。在一个包含五个节点的集群中,三个节点将各自拥有一个副本,但另外两个节点将不托管任何副本。
声明后,仲裁队列可以像任何其他 RabbitMQ 队列一样绑定到任何交换机。
如果使用 管理 UI 进行声明,则必须使用队列类型下拉菜单指定队列类型。
客户端操作
以下操作对于仲裁队列和经典队列的工作方式相同
在某些队列操作中,存在细微差异
复制因子和成员资格管理
声明仲裁队列时,必须在集群中启动其初始副本数。默认情况下,要启动的副本数最多为三个,每个 RabbitMQ 节点一个。
对于仲裁队列,三个节点是副本的实际最小值。在具有更多节点的 RabbitMQ 集群中,添加比 仲裁(多数)更多的副本不会在 仲裁队列可用性 方面提供任何改进,但会消耗更多集群资源。
因此,仲裁队列的建议副本数是集群节点的仲裁(但不应少于三个)。这假设一个 完全形成的 至少包含三个节点的集群。
控制初始复制因子
例如,一个包含三个节点的集群将具有三个副本,每个节点上一个副本。在一个包含七个节点的集群中,三个节点将各自拥有一个副本,但另外四个节点将不托管新声明队列的任何副本。
可以为仲裁队列配置复制因子(队列拥有的副本数)。
有实际意义的最小因子值是三。强烈建议该因子为奇数。这样可以计算出清晰的仲裁(多数)。例如,在两个节点集群中,没有节点的“多数”。这将在下面 容错和在线副本的最小数量 部分的更多示例中介绍。
对于较大的集群或具有偶数节点的集群,这可能不是理想的选择。要控制仲裁队列成员的数量,请在声明队列时设置 x-quorum-initial-group-size
队列参数。提供的组大小参数应为大于零且小于或等于当前 RabbitMQ 集群大小的整数。仲裁队列将启动以在声明时集群中存在的 RabbitMQ 节点的随机子集上运行。
如果仲裁队列在所有集群节点加入集群之前声明,并且初始副本计数大于集群成员总数,则使用的有效值将等于集群节点总数。当更多节点加入集群时,副本计数不会自动增加,但可以由操作员增加。
管理副本
仲裁队列的副本由操作员显式管理。当新节点添加到集群时,除非操作员显式将其添加到仲裁队列或一组仲裁队列的成员(副本)列表中,否则它不会托管任何仲裁队列副本。
当节点必须退役(从集群中永久移除)时,forget_cluster_node
命令将自动尝试移除退役节点上的所有仲裁队列成员。或者,可以在节点移除之前使用下面的 shrink
命令,将任何副本移动到新节点。
另请参阅 持续成员资格协调,了解更自动地增加仲裁队列的方式。
提供了多个 CLI 命令 来执行上述操作
rabbitmq-queues add_member [-p <vhost>] <queue-name> <node>
rabbitmq-queues delete_member [-p <vhost>] <queue-name> <node>
rabbitmq-queues grow <node> <all | even> [--vhost-pattern <pattern>] [--queue-pattern <pattern>]
rabbitmq-queues shrink <node> [--errors-only]
为了成功添加和移除成员,队列中必须有仲裁数量的副本可用,因为成员资格更改被视为队列状态更改。
需要注意不要在执行涉及成员资格更改的维护操作时,因失去仲裁而意外地使队列不可用。
当替换集群节点时,更安全的方法是首先添加一个新节点,增加任何需要在新节点上设置成员的仲裁队列,然后退役它所替换的节点。
队列领导者位置
每个仲裁队列都有一个主副本。该副本称为队列领导者。所有队列操作首先通过领导者,然后复制到追随者。这对于保证消息的 FIFO 顺序是必要的。
为了避免集群中的某些节点托管大多数队列领导者副本,从而处理大部分负载,队列领导者应合理地均匀分布在集群节点之间。
当声明新的仲裁队列时,将随机选择将托管其副本的节点集,但始终包括声明队列的客户端所连接的节点。
可以使用三个选项控制哪个副本成为初始领导者
- 设置
queue-leader-locator
策略 键(推荐) - 通过在 配置文件 中定义
queue_leader_locator
键(推荐) - 使用
x-queue-leader-locator
可选队列参数
支持的队列领导者定位器值包括
client-local
:选择声明队列的客户端所连接的节点。这是默认值。balanced
:如果队列总数(经典队列、仲裁队列和流)少于 1000 个,则选择托管最少仲裁队列领导者的节点。如果队列总数超过 1000 个,则随机选择一个节点。
重新平衡副本
一旦声明,RabbitMQ 仲裁队列领导者可能会在 RabbitMQ 集群中分布不均。要重新平衡,请使用 rabbitmq-queues rebalance
命令。重要的是要知道,这不会更改仲裁队列跨越的节点。要修改成员资格,请参阅 管理副本。
# rebalances all quorum queues
rabbitmq-queues rebalance quorum
可以重新平衡按名称选择的队列子集
# rebalances a subset of quorum queues
rabbitmq-queues rebalance quorum --queue-pattern "orders.*"
或特定虚拟主机集中的仲裁队列
# rebalances a subset of quorum queues
rabbitmq-queues rebalance quorum --vhost-pattern "production.*"
持续成员资格协调 (CMR)
持续成员资格协调 (CMR) 功能是对 显式副本管理 的补充,而不是替代。在某些节点从集群中永久移除的情况下,可能仍然需要显式移除仲裁队列副本。
除了使用初始目标大小和 显式副本管理 来控制仲裁队列副本成员资格外,还可以通过启用持续成员资格协调功能,将节点配置为自动尝试将仲裁队列副本成员资格增加到配置的目标组大小。
激活后,每个仲裁队列领导者副本将定期检查其当前成员组大小(在线副本数),并将其与目标值进行比较。
如果队列低于目标值,RabbitMQ 将尝试将队列扩展到当前未托管该队列副本的可用节点上(如果有),直到达到目标值。
何时触发持续成员资格协调?
默认的协调间隔为 60 分钟。此外,集群中的某些事件(例如,添加新节点、永久节点移除或仲裁队列相关的策略更改)也会触发自动协调。
请注意,节点或仲裁队列副本故障不会触发自动成员资格协调。
如果节点以不可恢复的方式发生故障并且无法恢复,则必须从集群中显式移除该节点,或者操作员必须选择启用 quorum_queue.continuous_membership_reconciliation.auto_remove
设置。
这也意味着 升级 不会触发自动成员资格协调,因为节点预计会恢复,并且一次只停止少数(通常只有一个)节点进行升级。
CMR 配置
rabbitmq.conf
rabbitmq.conf 配置键 | 描述 |
| 启用或禁用持续成员资格协调。
|
| 队列成员的目标副本计数(组大小)。
|
| 启用或禁用自动移除不再是集群一部分,但仍然是仲裁队列成员的成员节点。
|
| 默认评估间隔(毫秒)。
|
| 触发事件发生时(例如,添加或从集群中移除节点或适用的策略更改)使用的协调延迟(毫秒)。此延迟将仅应用一次,然后将再次使用常规间隔。
|
策略键
策略键 | 描述 |
| 定义匹配队列的目标副本计数(组大小)。此策略可以由用户和操作员设置。
|
可选参数键 | 描述 |
| 定义匹配队列的目标副本计数(组大小)。此键可以被操作员策略覆盖。
|
仲裁队列行为
仲裁队列依赖于称为 Raft 的共识协议来确保数据一致性和安全性。
每个仲裁队列都有一个主副本(Raft 术语中的领导者)和零个或多个辅助副本(称为追随者)。
领导者在集群首次形成时选举产生,以后在领导者不可用时也会选举产生。
领导者选举和故障处理
仲裁队列需要声明的节点中的仲裁数量可用才能正常运行。当托管仲裁队列领导者的 RabbitMQ 节点发生故障或停止时,将选举托管该仲裁队列追随者的另一个节点作为领导者并恢复操作。
发生故障并重新加入的追随者将与领导者重新同步(“追赶”)。对于仲裁队列,临时的副本故障不需要从当前选举的领导者进行完全重新同步。如果重新加入的副本落后于领导者,则只会传输增量。此“追赶”过程不会影响领导者的可用性。
当 添加 新副本时,它将从领导者同步整个队列状态。
容错和在线副本的最小数量
共识系统可以在数据安全方面提供某些保证。这些保证确实意味着需要满足某些条件才能使其相关,例如需要至少三个集群节点来提供容错能力,并且需要超过一半的成员可用才能工作。
各种规模的集群的容错特性可以用表格描述
集群节点计数 | 容忍的节点故障数 | 对网络分区具有容错能力 |
---|---|---|
1 | 0 | 不适用 |
2 | 0 | 否 |
3 | 1 | 是 |
4 | 1 | 是,如果大多数存在于一侧 |
5 | 2 | 是 |
6 | 2 | 是,如果大多数存在于一侧 |
7 | 3 | 是 |
8 | 3 | 是,如果大多数存在于一侧 |
9 | 4 | 是 |
如上表所示,节点数少于三个的 RabbitMQ 集群无法完全受益于仲裁队列保证。具有偶数个 RabbitMQ 节点的 RabbitMQ 集群无法受益于仲裁队列成员分布在所有节点上。对于这些系统,仲裁队列大小应限制为较小的奇数个节点。
对于节点大小大于 5 的仲裁队列,性能会大幅下降。我们不建议在超过 7 个 RabbitMQ 节点上运行仲裁队列。默认仲裁队列大小为 3,可以使用 x-quorum-initial-group-size
队列参数 控制。
数据安全
仲裁队列旨在在网络分区和故障情况下提供数据安全。只要至少大多数托管仲裁队列的 RabbitMQ 节点不是永久不可用,使用 发布者确认 功能成功确认回发布者的消息就不应丢失。
通常,仲裁队列优先考虑数据一致性而不是可用性。
对于尚未 确认给发布者 的消息,仲裁队列无法提供任何安全保证。此类消息可能会“在传输中”、在操作系统缓冲区中丢失,或者未能到达目标节点或队列领导者。
可用性
仲裁队列应能够容忍少数队列成员变得不可用,而对可用性没有或几乎没有影响。
请注意,根据使用的 分区处理策略,RabbitMQ 可能会在恢复期间重启自身并重置节点,但只要这种情况没有发生,此可用性保证就应该成立。
例如,具有三个副本的队列可以容忍一个节点故障而不会失去可用性。具有五个副本的队列可以容忍两个,依此类推。
如果无法恢复仲裁数量的节点(例如,如果 3 个 RabbitMQ 节点中有 2 个永久丢失),则队列将永久不可用,并且需要强制删除并重新创建。
与领导者断开连接或参与领导者选举的仲裁队列追随者副本将忽略发送给它的队列操作,直到它们意识到新选举的领导者。日志中将出现关于此类事件的警告(received unhandled msg
和类似内容)。一旦副本发现新选举的领导者,它将从领导者同步它没有的队列操作日志条目,包括丢弃的条目。因此,仲裁队列状态将保持一致。
性能特征
仲裁队列在 RabbitMQ 4.1.x 中得到了进一步优化,现在 具有更低的内存占用和更高的消费者交付率,以及峰值负载下的并行性。
仲裁队列旨在权衡延迟以换取吞吐量,并在 3、5 和 7 节点配置中针对几种不同的消息大小进行了测试。
在使用消费者确认和发布者确认的场景中,已观察到仲裁队列比经典镜像队列(2021 年已弃用,在 RabbitMQ 4.0 中已移除)具有更高的吞吐量。例如,查看 这些使用 3.10 的基准测试 和 另一个使用 3.12 的基准测试。
由于仲裁队列在执行任何操作之前都会将所有数据持久化到磁盘,因此建议使用尽可能快的磁盘和某些 性能调优 设置。
仲裁队列还得益于消费者使用更高的预取值,以确保在确认通过系统流动时,消费者不会被饿死,并允许消息及时交付。
由于仲裁队列的磁盘 I/O 密集型特性,它们的吞吐量会随着消息大小的增加而降低。
仲裁队列吞吐量也受副本数量的影响。仲裁队列拥有的副本越多,其吞吐量通常越低,因为复制数据和达成共识需要做更多的工作。
可配置设置
有一些新的配置参数可以使用 高级 配置文件进行调整。
请注意,与 资源占用 相关的所有设置都在单独的部分中记录。
ra
应用程序(即仲裁队列使用的 Raft 库)有 自己的一组可调参数。
rabbit
应用程序有几个可用的仲裁队列相关配置项。
advanced.config 配置键 | 描述 | 默认值 |
rabbit.quorum_cluster_size | 设置默认仲裁队列集群大小(可以在声明时被 | 3 |
rabbit.quorum_commands_soft_limit | 这是一个与流量控制相关的参数,定义通道在进入流量控制之前接受的最大未确认消息数。当前的默认配置旨在在有多个发布者向同一个仲裁队列发送消息时提供良好的性能和稳定性。如果应用程序通常每个队列只有一个发布者,则可以增加此限制以提供稍好的入口速率。 | 32 |
仲裁队列配置示例
以下 rabbitmq.conf
示例修改了上面列出的所有值
quorum_queue.initial_cluster_size = 3
quorum_queue.commands_soft_limit = 32
放宽属性等效性检查的选项
当客户端重新声明队列时,RabbitMQ 节点会 执行属性等效性检查。如果某些属性不等效,则声明将失败并出现 通道错误。
在应用程序通过 x-queue-type
参数显式设置队列类型,并且无法快速更新和/或重新部署的环境中,可以使用选择加入设置忽略对 x-queue-type
的等效性检查
quorum_queue.property_equivalence.relaxed_checks_on_redeclaration = true
如果 quorum_queue.property_equivalence.relaxed_checks_on_redeclaration
设置为 true
,则在队列重新声明时,将忽略 'x-queue-type' 标头(不比较等效性)。
这可以简化显式将 'x-queue-type' 设置为 'classic' 的应用程序的升级,这出于历史原因,但未设置任何其他可能冲突或显着更改队列行为和语义的属性,例如 'exclusive' 字段。
资源使用
仲裁队列在 RabbitMQ 4.1.x 中得到了进一步优化,现在 具有更低的内存占用和更高的消费者交付率,以及峰值负载下的并行性。
仲裁队列针对数据安全性和性能进行了优化。每个仲裁队列进程维护队列中消息的内存索引,每条消息至少需要 32 字节的元数据(如果消息被退回或设置了 TTL,则需要更多)。因此,每个仲裁队列进程对于队列中的每 30000 条消息将至少使用 1MB 内存(消息大小无关紧要)。您可以根据队列数量和队列中预期或最大消息数执行粗略计算。保持队列短是维持低内存使用率的最佳方法。如果队列因任何原因变长,为所有队列设置最大队列长度 是限制总内存使用量的好方法。
内存、WAL 和段文件如何交互
给定节点上的仲裁队列为所有操作共享一个预写日志 (WAL)。WAL 包含队列上有限数量的近期操作。WAL 条目既存储在内存中,又写入磁盘。当当前 WAL 文件达到预定义的限制时,它会被刷新到节点上每个仲裁队列成员的段文件中,并且系统将开始释放该批日志条目使用的内存。
然后,随着消费者 确认交付,段文件会随着时间推移进行压缩。压缩是回收磁盘空间的过程。
可以控制 WAL 文件大小限制,达到此限制时,WAL 文件将被刷新到磁盘
# Flush current WAL file to a segment file on disk once it reaches 64 MiB in size
raft.wal_max_size_bytes = 64000000
该值默认为 512 MiB。这意味着在稳定负载期间,WAL 表内存占用可能达到 512 MiB。
由于 运行时 不能保证立即释放内存,我们建议为 RabbitMQ 节点分配至少 3 倍于有效 WAL 文件大小限制的内存。在高吞吐量系统中,将需要更多内存。对于这些系统,4 倍是一个好的起点。
仲裁队列的内存占用模式通常如下所示
磁盘空间
在仲裁队列使用量大和/或通过它们的流量大的消息的环境中,超额配置磁盘空间非常重要,即拥有额外的备用容量。这遵循现代发布系列的 存储的一般建议。
对于大型消息(例如,1 MiB 及以上),仲裁队列磁盘占用空间可能很大。根据工作负载——特别是等待 消费者确认 的消息数量——段文件的移除可能会缓慢进行,从而导致磁盘占用空间不断增长。
这导致以下几点建议
- 在仲裁队列使用量大和/或通过它们的流量大的消息的环境中,超额配置磁盘空间非常重要,即拥有额外的备用容量
- 仲裁队列在很大程度上依赖于消费者及时确认消息,因此必须使用 合理的低交付确认超时
- 对于较大的消息,减少每个段文件的条目数 可能有利于减小段文件的大小,并允许更频繁地截断(移除)这些文件
- 可以将较大的消息存储在 blob 存储中作为替代方案,并在流经仲裁队列的消息中传递相关的元数据
何时发生段文件截断?
段文件截断会定期响应客户端操作而发生,当这样做是安全的时候。仲裁队列定期进行检查点和快照,并截断已知不再包含任何“活动”(准备交付或等待消费者确认)消息的段文件。
当没有客户端活动时,这些事件不会发生,段文件截断也不会发生。如果队列完全空闲且为空,但有大量来自早期峰值活动期间的磁盘段文件,则确保队列为空,然后清除它可能有助于强制段文件截断。
要清除队列,请使用
- 使用管理 UI 中队列页面上的
清除消息
按钮 - 当使用 AMQP 0-9-1 时,
queue.purge
操作,通过大多数 AMQP 0-9-1 客户端库中类似命名的函数或方法公开 - 当使用 AMQP 1.0 时,由 Team RabbitMQ 维护的 AMQP 1.0 客户端库中类似命名的函数或方法
请注意,清除具有未确认交付的队列不会对所有段文件产生预期效果,甚至可能根本没有效果。
重复重新排队的交付(交付-重新排队循环)
在内部,仲裁队列使用日志实现,其中所有操作(包括消息)都持久化。为了避免此日志增长过大,需要定期截断它。为了能够截断日志的某个部分,需要确认该部分中的所有消息。连续 拒绝或 nack 同一条消息并将 requeue
标志设置为 true 的使用模式可能会导致日志以无界方式增长,并最终填满磁盘。因此,自 RabbitMQ 4.0 以来,始终设置默认的 delivery-limit
为 20,之后消息将被丢弃或死信。
被拒绝或 nack 回仲裁队列的消息将返回到队列的尾部,如果未设置 delivery-limit。这避免了上述重复重新排队导致 Raft 日志以无界方式增长的场景。如果设置了 delivery-limit
,它将使用原始行为,即将消息返回到队列头部附近。
旧的无限制 delivery-limit 行为可以通过设置队列参数或策略,并将 delivery limit 设置为 -1 来恢复。不建议这样做,但在某些罕见情况下,可能需要为了 3.13.x 兼容性而这样做。
原子使用量增加
仲裁队列的内部实现将队列名称转换为 Erlang 原子。如果不断创建和删除具有任意名称的队列,则可能会威胁 RabbitMQ 系统的长期稳定性,如果原子表的大小达到 500 万的默认限制。
虽然仲裁队列并非设计用于高 churn 环境(非镜像经典队列是这些环境的最佳选择),但如果确实有必要,可以增加此限制。
请参阅 运行时指南 以了解更多信息。
性能调优
仲裁队列在 RabbitMQ 4.1.x 中得到了进一步优化,现在 具有更低的内存占用和更高的消费者交付率,以及峰值负载下的并行性。
本节旨在介绍一些可调参数,这些参数可能会提高某些工作负载的仲裁队列吞吐量。其他工作负载可能看不到任何增加,或者观察到吞吐量随着这些设置而降低。
此处的值和建议仅作为起点,并进行您自己的基准测试(例如,使用 PerfTest)以得出结论,哪种值组合最适合特定的工作负载。
调优:Raft 段文件条目计数
对于小消息
具有小消息和较高消息速率的工作负载可以从以下配置更改中受益,该更改增加了段文件中允许的 Raft 日志条目数(例如,入队的消息)。
当从具有长积压的队列中消费时,拥有较少的段文件可能是有益的
# Positive values up to 65535 are allowed, the default is 4096.
# This value is reasonable for workloads with small (say, smaller than 8 kiB) messages
raft.segment_max_entries = 32768
不支持 大于 65535
的值。
对于大消息
具有大型消息(100 多千字节甚至几 MiB)的工作负载将受益于以下配置更改,该更改显着减少了段文件中允许的 Raft 日志条目数(例如,入队的消息)。
每个段文件的条目数显着减少将使每个段文件的大小保持合理,并允许节点以更高的速率截断它们,因为每个段文件具有少量活动消息的可能性较低,而这些活动消息会保留整个文件。
作为粗略计算,考虑一个工作负载,它入队 4096 条消息,每条消息 1 MiB。这将导致段文件大小超过 4 GiB,并且只要这些消息中的至少一条是活动的(未交付和确认),整个文件就无法删除。
例如,要仅允许每个段文件 128 个条目
# The default is 4096.
# This value is only reasonable for workloads with messages of 1 MiB or even larger
raft.segment_max_entries = 128
调优:Linux 预读
此外,上述具有更高小消息速率的工作负载可以从更高的 readahead
中受益,readahead
是 Linux 上存储设备的可配置块设备参数。
要检查有效的 readahead
值,请使用 blockdev --getra
并指定托管 RabbitMQ 节点数据目录的块设备
# This is JUST AN EXAMPLE.
# The name of the block device in your environment will be different.
#
# Displays effective readahead value device /dev/sda.
sudo blockdev --getra /dev/sda
要配置 readahead
,请对托管 RabbitMQ 节点数据目录的块设备使用 blockdev --setra
# This is JUST AN EXAMPLE.
# The name of the block device in your environment will be different.
# Values between 256 and 4096 in steps of 256 are most commonly used.
#
# Sets readahead for device /dev/sda to 4096.
sudo blockdev --setra 4096 /dev/sda