仲裁队列
概述
RabbitMQ 仲裁队列是一种现代队列类型,它基于 Raft 共识算法 实现了一个持久、复制的队列,应该被认为是在需要复制的高可用队列时的默认选择。
仲裁队列旨在实现优秀的数据安全,以及可靠、快速的领导者选举属性,以确保在升级或其他混乱情况下也能保持高可用性。
仲裁队列针对某些 用例 进行了优化,在这些用例中,数据安全 是最高优先级。这在 动机 部分进行了介绍。
与经典队列相比,仲裁队列在 行为上存在差异,并且还有一些 限制,在将应用程序从使用经典队列转换为使用仲裁队列时,务必注意这些限制。
某些功能是仲裁队列独有的,例如 中毒消息处理、至少一次死信 以及使用 AMQP 时产生的 modified
结果。
对于那些可以从复制和可重复读中受益的用例,流 可能是比仲裁队列更好的选择。
仲裁队列和 流 是两种可用的复制数据结构。从 RabbitMQ 4.0 开始,已移除经典队列镜像功能。
涵盖的主题
本文档涵盖的主题包括
- 什么是仲裁队列 以及为什么引入它们
- 它们与经典队列有什么区别
- 仲裁队列的主要 用例 以及何时不使用它们
- 如何 声明仲裁队列
- 复制 相关主题:副本管理、副本领导者再平衡、最佳副本数量等
- 仲裁队列在 领导者故障处理、数据安全 和 可用性 方面提供了哪些保证
- 持续的 成员资格协调
- 性能 特征以及与它们相关的 性能调优
- 中毒消息处理(故障重传循环保护)
- 仲裁队列的可配置设置
- 仲裁队列的资源使用情况,最重要的是它们的 内存占用
等等。
在了解有关仲裁队列的更多信息时,对 RabbitMQ 集群 的一般了解将有所帮助。
动机
仲裁队列采用不同的复制和共识协议,并放弃了对某些本质上“短暂”功能的支持,这导致了一些限制。这些限制将在本文档的后面部分介绍。
仲裁队列通过了 重构后更加严格的版本 的 原始 Jepsen 测试。这确保了它们在网络分区和故障场景下按预期行为。新的测试持续运行以发现可能的回归,并且定期进行增强以测试新功能(例如 死信)。
什么是仲裁?
如果刻意简化,仲裁 在分布式系统中可以定义为大多数节点之间达成的协议((N/2)+1
,其中 N
是系统参与者的总数)。
将其应用于 RabbitMQ 集群 中的队列镜像时,这意味着大多数副本(包括当前选定的队列领导者)都同意队列的状态及其内容。
用例
仲裁队列的预期用途是在拓扑结构中,其中队列长期存在,并且对应用程序体系结构的某些方面至关重要。
一些良好的用例示例包括销售系统中的传入订单或选举系统中投出的选票,其中可能丢失的消息会对整个系统的正确性和功能产生重大影响。
仲裁队列不设计用于解决所有问题。股票行情、即时消息系统和 RPC 回复队列从使用仲裁队列中获益很少或根本没有获益。
发布者应该 使用发布者确认,因为这是客户端与仲裁队列共识系统交互的方式。发布者确认 仅在 已成功将已发布的消息复制到大多数副本并且在队列上下文中被视为“安全”后才发出。
消费者应该使用 手动确认 以确保无法成功处理的消息将返回到队列,以便其他消费者可以重新尝试处理。
何时不使用仲裁队列
在某些情况下,不应使用仲裁队列。它们通常涉及
- 临时队列:短暂队列或独占队列,高队列更新率(声明和删除速率)
- 最低可能的延迟:底层共识算法由于其数据安全功能而具有固有的更高延迟
- 当数据安全不是优先事项时(例如,应用程序不使用 手动确认 并且不使用发布者确认)
- 非常长的队列积压(超过 500 万条消息)(流 可能更适合)
- 大扇出:(流 可能更适合)
功能
仲裁队列与其他 队列 类型共享大多数基本原理。
与经典队列的比较
以下操作对于仲裁队列和经典队列的工作方式相同
某些队列操作存在细微差异
- 声明
- 为消费者设置预取
功能矩阵
功能 | 经典队列 | 仲裁队列 |
---|---|---|
非持久队列 | 是 | 否 |
消息复制 | 否 | 是 |
排他性 | 是 | 否 |
每条消息的持久性 | 每条消息 | 始终 |
成员资格变更 | 否 | 半自动 |
消息 TTL(生存时间) | 是 | 是 |
队列 TTL | 是 | 部分(在重新声明队列时不续期租约) |
队列长度限制 | 是 | 是(除了 x-overflow : reject-publish-dlx ) |
将消息保留在内存中 | 参见 经典队列 | 从不(参见 资源使用) |
消息优先级 | 是 | 是 |
单个活动消费者 | 是 | 是 |
消费者排他性 | 是 | 否(使用 单个活动消费者) |
消费者优先级 | 是 | 是 |
死信交换机 | 是 | 是 |
遵守 策略 | 是 | 是(参见 策略支持) |
中毒消息处理 | 否 | 是 |
全局 QoS 预取 | 是 | 否 |
服务器命名队列 | 是 | 否 |
现代仲裁队列还针对许多工作负载提供了 更高的吞吐量和更少的延迟变化。
队列和每条消息的 TTL
仲裁队列同时支持 队列 TTL 和消息 TTL(从 RabbitMQ 3.10 开始)(包括 队列中的每队列消息 TTL 和 发布者中的每条消息 TTL)。使用任何形式的消息 TTL 时,内存开销将每条消息增加 2 字节。
长度限制
仲裁队列支持 队列长度限制。
drop-head
和 reject-publish
溢出行为受支持,但它们不支持 reject-publish-dlx
配置,因为仲裁队列采用与经典队列不同的实现方法。
reject-publish
溢出行为的当前实现并不会严格执行限制,并且允许仲裁队列至少超过其限制一条消息,因此在需要精确限制的场景中应谨慎使用。
当仲裁队列达到最大长度限制并且配置了 reject-publish
时,它会通知每个发布通道,这些通道从那时起将拒绝所有消息并将其返回给客户端。这意味着仲裁队列可能会超过其限制一定数量的消息,因为在通道收到通知时,可能存在正在传输中的消息。队列接受的额外消息数量将根据此时正在传输中的消息数量而有所不同。
死信
仲裁队列支持死信交换 (DLX)。
传统上,在集群环境中使用 DLX 并不安全。
从 RabbitMQ 3.10 开始,仲裁队列支持一种更安全的死信方式,该方式对队列之间消息传输使用 至少一次
保证(有以下限制和注意事项)。
这是通过实现一个特殊的内部死信消费者进程来完成的,该进程与普通的队列消费者使用手动确认类似,除了它只消费已经被死信的消息。
这意味着源仲裁队列将保留死信消息,直到它们被确认。内部消费者将消费死信消息并使用发布者确认将它们发布到目标队列。它只有在收到发布者确认后才会确认,从而提供 至少一次
保证。
最多一次
仍然是仲裁队列的默认死信策略,它适用于死信消息更多地具有信息性质的场景,以及当消息丢失在队列之间传输时并不重要,或者当以下概述的溢出配置限制不适用时。
激活至少一次死信
要为源仲裁队列激活或打开 至少一次
死信,请应用以下所有策略(或以 x-
开头的等效队列参数)。
- 将
dead-letter-strategy
设置为at-least-once
(默认值为at-most-once
)。 - 将
overflow
设置为reject-publish
(默认值为drop-head
)。 - 配置
dead-letter-exchange
。 - 打开功能标志
stream_queue
(在 3.9 或更高版本中创建的 RabbitMQ 集群中默认打开)。
建议另外配置 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”标头中公开它。
当一条消息被重新传递的次数超过限制时,该消息将被丢弃(删除)或死信(如果配置了 DLX)。
建议所有仲裁队列都具有某种死信配置,以确保消息不会意外被丢弃和丢失。使用单个流用于低优先级死信策略是确保丢弃的消息在一段时间后保留的良好、低资源的方式。
从 RabbitMQ 4.0 开始,仲裁队列的传递限制默认为 20。
3.13.x 时代的行为,即没有限制,可以通过使用可选队列参数在声明时设置为 -1
,或者使用策略来恢复,如下所示。
有关更多详细信息,请参见重复重新排队。
配置限制
可以使用策略参数 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
>Add / update a policy
。在“名称”旁边输入策略名称(例如“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
>Add / update a policy
。在“名称”旁边输入策略名称(例如“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
>Add / update a policy
。在“名称”旁边输入策略名称(例如“qq-overrides”),在“模式”旁边输入模式(例如“^qq.”),并使用“应用于”下拉菜单选择策略应该应用于哪种类型的实体(在本例中为仲裁队列)。
在第一行
Policy
旁边,将 "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
)。此参数必须在队列声明时由客户端提供;不能使用策略设置或更改它。这是因为策略定义或适用的策略可以动态更改,但队列类型不能。它必须在声明时指定。
声明一个 x-queue-type
参数设置为 quorum
的队列将声明一个具有最多三个副本的仲裁队列(默认复制因子),每个集群节点一个。
例如,一个包含三个节点的集群将有三个副本,每个节点一个。在一个包含五个节点的集群中,三个节点将分别有一个副本,但两个节点不会托管任何副本。
声明后,仲裁队列可以像任何其他 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
等)。一旦副本发现新选定的领导者,它将从领导者同步它没有的队列操作日志条目,包括已删除的条目。因此,仲裁队列状态将保持一致。
性能特征
仲裁队列旨在以延迟换取吞吐量,并且已经在 3、5 和 7 节点配置中进行了测试,并使用了多种不同的消息大小。
在使用消费者确认和发布者确认的场景中,仲裁队列的吞吐量明显优于经典的镜像队列(在 2021 年已弃用,在 2024 年的 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 |
仲裁队列配置示例
以下 advanced.config
示例修改了上面列出的所有值
[
%% five replicas by default, only makes sense for nine node clusters
{rabbit, [{quorum_cluster_size, 5},
{quorum_commands_soft_limit, 64}]}
].
资源使用
仲裁队列针对数据安全性和性能进行了优化。每个仲裁队列进程都会维护一个队列中消息的内存索引,这需要至少 32 字节的元数据来表示每条消息(如果消息被返回或设置了 TTL,则更多)。因此,仲裁队列进程将至少使用 1MB 来表示队列中的每 30000 条消息(消息大小无关)。您可以根据队列数量和预期或最大消息数量来执行估算。保持队列较短是保持低内存使用量的最佳方法。 设置所有队列的最大队列长度 是限制总内存使用量的有效方法,尤其是在队列由于任何原因变长时。
此外,给定节点上的仲裁队列共享所有操作的预写式日志 (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 倍是一个良好的起点。
重复重新排队
仲裁队列在内部使用日志实现,其中所有操作(包括消息)都将被持久化。为了避免该日志增长过大,需要定期对其进行截断。为了能够截断日志的一部分,该部分中的所有消息都需要被确认。持续拒绝或 nack 同一消息(将 requeue
标志设置为 true)的使用模式会导致日志无限制地增长,最终填满磁盘。因此,从 RabbitMQ 4.0 开始,始终设置默认的 delivery-limit
为 20,之后消息将被丢弃或进入死信队列。
被拒绝或 nack 回到仲裁队列的消息将被返回到队列的末尾,如果没有设置传递限制。这避免了上述场景,其中重复的重新排队会导致 Raft 日志无限制地增长。如果设置了 delivery-limit
,它将使用将消息返回到队列头部的原始行为。
可以通过设置队列参数或策略(传递限制为 -1)来恢复旧的无限制传递限制行为。不建议这样做,但可能在某些罕见情况下需要用于 3.13.x 兼容性。
增加原子使用量
仲裁队列的内部实现将队列名称转换为 Erlang 原子。如果不断创建和删除具有任意名称的队列,可能会威胁到 RabbitMQ 系统的长期稳定性,如果原子表的大小达到默认限制 500 万。
虽然仲裁队列并非为高 churn 环境(非镜像经典队列是这些环境的最佳选择)而设计,但如果确实需要,可以增加限制。
请参阅运行时指南以了解更多信息。
性能调整
本节旨在介绍一些可调参数,这些参数可能会提高某些工作负载的仲裁队列的吞吐量。其他工作负载可能不会看到任何增加,或者在使用这些设置时观察到吞吐量下降。
将此处的值和建议作为起点,并进行自己的基准测试(例如,使用 PerfTest)来确定哪种值组合最适合特定工作负载。
调整:Raft 段文件条目计数
具有较小消息和较高消息速率的工作负载可以从以下配置更改中受益,该更改增加了段文件中允许的 Raft 日志条目(例如,排队的消息)数量。当从具有长积压的队列中消费时,具有较少的段文件可能是有益的
# Positive values up to 65535 are allowed, the default is 4096.
raft.segment_max_entries = 32768
不支持大于 65535
的值。
调整: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
,请使用 blockdev --setra
针对托管 RabbitMQ 节点数据目录的块设备
# 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