经典队列镜像(已弃用)
本指南介绍了一个已弃用功能,计划在下一个版本(RabbitMQ 4.0)中删除。
等等,还有更好的方法:现代复制队列类型和流
本指南介绍了一个已弃用并计划删除的功能:镜像(队列内容复制)经典队列。仲裁队列 或 流 应该代替镜像经典队列使用。
仲裁队列是一种更高级的队列类型,它使用复制提供高可用性,并专注于数据安全。仲裁队列支持消息 TTL 并提供更高的吞吐量和更稳定的延迟 与镜像经典队列相比。请现在从镜像经典队列迁移到仲裁队列。
流 是 RabbitMQ 支持的一种替代消息数据结构。与仲裁队列一样,流也是复制的。
仲裁队列应该是复制队列类型的默认选择。经典队列镜像将在 RabbitMQ 的未来版本中删除:经典队列将作为支持的非复制队列类型保留。
经典队列版本 2 可以与镜像一起使用。但是,不建议组合使用 v1 和 v2 成员。如果某些节点默认使用版本 1,而其他节点默认使用版本 2(如果未显式设置,新的镜像将使用本地节点的默认版本),则可能会发生这种情况。版本 2 在许多情况下明显更快,并且可能使 v1 镜像过载。最简单的解决方案是在更改配置中的默认版本之前,使用策略切换到版本 2。
概述
本指南涵盖的主题包括
- 下一代复制队列和流,以及为什么它们应该优先于经典队列镜像
- 什么是经典队列镜像 以及它的工作原理
- 如何启用它
- 哪些镜像设置可用
- 推荐哪些复制因子
- 数据局部性
- 领导者选举(镜像提升)和未同步镜像
- 在节点故障的情况下,镜像与非镜像队列行为 相比
- 批量同步 新添加和恢复的镜像
等等。
本指南假设您对RabbitMQ 集群 有基本了解。
什么是队列镜像
默认情况下,RabbitMQ 集群中队列的内容位于单个节点(声明队列的节点)上。这与交换器和绑定不同,交换器和绑定始终可以被视为位于所有节点上。队列可以选择在其他集群节点上运行镜像(附加副本)。
每个镜像队列由一个领导者副本和一个或多个镜像(副本)组成。领导者托管在一个通常称为该队列的领导者节点的节点上。每个队列都有自己的领导者节点。给定队列的所有操作首先应用于队列的领导者节点,然后传播到镜像。这包括将发布排队、将消息传递给消费者、跟踪来自消费者的确认 等等。
队列镜像意味着节点集群。因此不建议跨 WAN 使用它(尽管当然,客户端仍然可以从任何需要的距离进行连接)。
发布到队列的消息将复制到所有镜像。消费者连接到领导者,无论他们连接到哪个节点,镜像都会丢弃已在领导者处确认的消息。因此,队列镜像提高了可用性,但不会跨节点分配负载(所有参与的节点都执行所有工作)。
如果托管队列领导者的节点发生故障,最旧的镜像将被提升为新的领导者,只要它已同步。未同步镜像 也可以被提升,具体取决于队列镜像参数。
在分布式系统中,通常使用多个术语来标识主副本和辅助副本。本指南通常使用“领导者”来指代队列的主副本,使用“镜像”来指代辅助副本。
HTTP API 和 CLI 工具中的队列对象字段最初使用不幸的术语“从属”来指代辅助副本。该术语仍然出现在 CLI 工具中的列名中,以实现向后兼容,但将在未来版本中被替换或删除。
如何配置镜像
镜像参数使用策略 配置。策略根据名称(使用正则表达式模式)匹配一个或多个队列,并包含一个定义(可选参数的映射),这些参数将添加到匹配队列的属性总集中。
有关策略的更多信息,请参阅运行时参数和策略。
控制镜像的队列参数
如上所述,队列通过策略 启用镜像。策略可以随时更改;创建非镜像队列,然后在以后某个时间点将其设为镜像(反之亦然)是有效的。非镜像队列与没有镜像的镜像队列之间存在差异——前者缺少额外的镜像基础设施,并且可能会提供更高的吞吐量。
向队列添加镜像会增加集群负载,但有助于降低丢失所有最新副本 的可能性。
要使经典队列镜像,请创建一个与它们匹配的策略,并设置策略键 ha-mode
和(可选)ha-params
。下表解释了这些键的选项。
ha-mode | ha-params | 结果 |
---|---|---|
exactly | count | 集群中队列副本的数量(领导者加上镜像)。 count 值为 1 表示单个副本:仅队列领导者。如果运行队列领导者的节点不可用,行为取决于队列持久性。 count 值为 2 表示 2 个副本:1 个队列领导者和 1 个队列镜像。换句话说: 如果集群中的节点少于count,则将队列镜像到所有节点。如果集群中的节点多于count,并且包含镜像的节点宕机,则将在另一个节点上创建新的镜像。 |
all | (无) | 队列将跨集群中的所有节点镜像。当向集群添加新节点时,队列将被镜像到该节点。 此设置非常保守。建议镜像到集群节点的仲裁(N/2 + 1),例如,在 3 节点集群中镜像到 2 个节点,或在 5 节点集群中镜像到 3 个节点。镜像到所有节点将给所有集群节点带来额外的压力,包括网络 I/O、磁盘 I/O 和磁盘空间使用。 |
nodes | 节点名称 | 队列将镜像到节点名称中列出的节点。节点名称是 Erlang 节点名称,如 如果这些节点名称中任何一个都不属于集群的一部分,这不会构成错误。如果列表中的任何节点在声明队列时都未在线,则该队列将在声明客户端连接到的节点上创建。 |
每当队列的 HA 策略更改时,它将尽力保留其现有镜像,只要这与新策略相符即可。
复制因子:最佳镜像数量是多少?
镜像到所有节点是最保守的选项。它将给所有集群节点带来额外的压力,包括网络 I/O、磁盘 I/O 和磁盘空间使用。在大多数情况下,没有必要在每个节点上都进行复制。
对于 3 个或更多节点的集群,建议复制到仲裁(多数)节点,例如,在 3 节点集群中复制到 2 个节点,或在 5 节点集群中复制到 3 个节点。
由于某些数据可能天生就是短暂的或对时间非常敏感,因此对某些队列使用较少的镜像(甚至不使用任何镜像)是完全合理的。
如何检查队列是否已镜像?
镜像队列将在管理 UI 的队列页面上在其旁边显示策略名称和额外副本(镜像)的数量。 管理 UI。
下面是名为 two.replicas
的队列的示例,它有一个领导者和一个镜像
队列的领导者节点及其在线镜像(如果有)将在队列页面上列出
如果队列页面未列出任何镜像,则该队列未镜像(或只有一个镜像未在线)
添加新的队列镜像时,事件将被记录
2018-03-01 07:26:33.121 [info] <0.1360.0> Mirrored queue 'two.replicas' in vhost '/': Adding mirror on node hare@warp10: <37324.1148.0>
可以使用 rabbitmqctl list_queues
列出队列领导者和镜像。在此示例中,我们还显示了队列策略,因为它与主题高度相关
# mirror_pids is a new field alias introduced in RabbitMQ 3.11.4
rabbitmqctl list_queues name policy pid mirror_pids
# => Timeout: 60.0 seconds ...
# => Listing queues for vhost / ...
# => two.replicas ha-two <[email protected]> [<[email protected]>]
如果预期镜像的队列未镜像,这通常意味着其名称与控制镜像的策略中指定的名称不匹配,或者其他策略优先(并且未启用镜像)。请参阅 运行时参数和策略 了解更多信息。
队列领导者副本、领导者迁移、数据局部性
队列领导者位置
本节已移至 集群指南。
"节点" 策略和迁移领导者
请注意,设置或修改“节点”策略会导致现有领导者消失,前提是它未列在新策略中。为了防止消息丢失,RabbitMQ 将保留现有领导者,直到至少一个其他镜像同步(即使这需要很长时间)。但是,一旦同步发生,事情将像节点失败一样进行:消费者将与领导者断开连接,并将需要重新连接。
例如,如果队列位于 [A B]
上(其中 A
是领导者),并且您为其提供一个 nodes
策略,告诉它位于 [C D]
上,它最初将位于 [A C D]
上。一旦队列在新的镜像 [C D]
上同步,A
上的领导者将关闭。
独占队列的镜像
声明独占队列的连接关闭时,将删除独占队列。因此,独占队列镜像(或非持久队列)没有用处,因为当托管它的节点出现故障时,连接将关闭,并且无论如何都需要删除队列。
因此,独占队列永远不会镜像(即使它们与表明应该镜像的策略匹配)。它们也永远不会持久(即使声明为持久)。
集群中非镜像队列的行为
本指南侧重于镜像队列,但是,重要的是简要说明非镜像队列在集群中的行为与镜像队列的行为形成对比。
如果队列的领导者节点(运行队列领导者的节点)可用,则可以在任何节点上执行所有队列操作(例如声明、绑定和消费者管理、消息路由到队列)。集群节点将透明地将操作路由到领导者节点,以供客户端使用。
如果队列的领导者节点不可用,则非镜像队列的行为取决于其持久性。持久队列将变得不可用,直到节点恢复。对具有不可用领导者节点的持久队列的所有操作都将失败,并在服务器日志中显示类似于以下消息的消息
operation queue.declare caused a channel exception not_found: home node 'rabbit@hostname' of durable queue 'queue-name' in vhost '/' is down or inaccessible
不可持久队列将被删除。
如果希望队列始终可用,则可以配置镜像以 即使不同步也提升为领导者。
示例
下面是一个策略,其中名称以“two.
”开头的队列将镜像到集群中的任意两个节点,并 自动同步
rabbitmqctl |
|
---|---|
rabbitmqctl (Windows) |
|
HTTP API |
|
Web UI |
|
rabbitmqctl | 警告 镜像到所有节点没有必要,会导致不必要的资源浪费。 考虑使用“ha-mode”:“exactly” 镜像到大多数(N/2+1)节点。见上文复制因子。
|
---|---|
rabbitmqctl (Windows) | 警告 镜像到所有节点没有必要,会导致不必要的资源浪费。 考虑使用“ha-mode”:“exactly” 镜像到大多数(N/2+1)节点。见上文复制因子。
|
HTTP API | 警告 镜像到所有节点没有必要,会导致不必要的资源浪费。 考虑使用“ha-mode”:“exactly” 镜像到大多数(N/2+1)节点。见上文复制因子。
|
Web UI | 警告 镜像到所有节点没有必要,会导致不必要的资源浪费。 考虑使用“ha-mode”:“exactly” 镜像到大多数(N/2+1)节点。见上文复制因子。
|
一个策略,其中名称以“nodes.
”开头的队列将镜像到集群中的特定节点
rabbitmqctl |
|
---|---|
rabbitmqctl (Windows) |
|
HTTP API |
|
Web UI |
|
镜像队列的实现和语义
如前所述,对于每个镜像队列,都存在一个领导者副本和多个镜像,每个镜像都在不同的节点上。镜像按与领导者完全相同的顺序应用发生在领导者上的操作,从而保持相同的状态。除发布之外的所有操作都只发送到领导者,然后领导者将操作效果广播到镜像。因此,从镜像队列中消费的客户端实际上是从领导者那里消费的。
如果镜像出现故障,除了做一些簿记外,几乎没有其他办法:领导者仍然是领导者,并且任何客户端都不需要采取任何措施或被告知故障。请注意,镜像故障可能不会立即被检测到,并且每连接流控制机制的中断可能会延迟消息发布。详细信息在 节点间通信心跳 指南中描述。
如果领导者出现故障,则将按如下方式将一个镜像提升为领导者
- 运行时间最长的镜像将被提升为领导者,假设它最有可能与领导者完全同步。如果没有与领导者 同步 的镜像,则仅存在于领导者上的消息将丢失。
- 镜像认为所有先前的消费者都已突然断开连接。它重新排队已传递给客户端但正在等待确认的所有消息。这可能包括客户端已发出确认的消息,例如,如果确认在到达托管队列领导者节点的网络上丢失,或者在从领导者广播到镜像时丢失。在这两种情况下,新的领导者别无选择,只能重新排队它未看到确认的所有消息。
- 已请求在队列故障转移时通知的消费者 将被告知取消。
- 由于重新排队,重新从队列消费的客户端 必须 注意到他们可能会随后收到他们已经收到的消息。
- 随着选定的镜像成为领导者,在此期间发布到镜像队列的所有消息都不会丢失(除非在提升的节点上出现后续故障)。发布到托管队列镜像的节点的消息将路由到队列领导者,然后复制到所有镜像。如果领导者出现故障,消息将继续发送到镜像,并在镜像提升为领导者完成之后添加到队列中。
- 使用 发布者确认 的客户端发布的消息,即使在消息发布到发布者收到确认之间领导者(或任何镜像)出现故障,仍将被确认。从发布者的角度来看,发布到镜像队列与发布到非镜像队列没有区别。
如果消费者使用 自动确认模式,则消息可能会丢失。当然,这与非镜像队列没有区别:代理认为消息在发送到自动确认模式下的消费者后立即确认。
如果客户端突然断开连接,则消息可能永远无法被接收。如果是镜像队列,如果领导者死亡,则正在传输到自动确认模式下的消费者的消息可能永远无法被这些客户端接收,并且不会被新的领导者重新排队。由于消费客户端可能连接到存活的节点,因此 消费者取消通知 有助于识别此类事件可能发生的时刻。当然,实际上,如果数据安全比吞吐量不那么重要,则自动确认模式是最佳选择。
发布者确认和事务
镜像队列支持 发布者确认 和事务。选择的语义是,在确认和事务的情况下,操作跨越队列的所有镜像。因此,在事务的情况下,tx.commit-ok
只有在事务已应用到队列的所有镜像后才会返回给客户端。同样,在发布者确认的情况下,只有在消息被所有镜像接受后,才会向发布者确认消息。可以认为语义与消息被路由到多个普通队列相同,并且具有类似地被路由到多个队列的发布的事务也是如此。
流量控制
RabbitMQ 使用基于信用的算法来 限制消息发布速率。发布者在从队列的所有镜像收到信用后才允许发布。在此上下文中,信用意味着发布许可。未能发出信用的镜像会导致发布者停滞。发布者将一直处于阻塞状态,直到所有镜像发出信用或直到剩余节点认为镜像已与集群断开连接。Erlang 通过定期向所有节点发送滴答来检测此类断开连接。可以使用 net_ticktime 配置设置控制滴答间隔。
领导者故障和消费者取消
从镜像队列中消费的客户端可能想知道他们一直在消费的队列已经失效转移。 当镜像队列失效转移时,关于哪些消息发送到哪个消费者的知识会丢失,因此所有未确认的消息都会重新投递,并设置了redelivered
标志。 消费者可能想知道这种情况会发生。
如果是这样,他们可以使用设置了x-cancel-on-ha-failover
参数为true
来消费。 然后,他们的消费会在失效转移时被取消,并且会发送一个消费者取消通知。 然后,消费者有责任重新发布basic.consume
以开始再次消费。
例如(在 Java 中)
Channel channel = ...;
Consumer consumer = ...;
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-cancel-on-ha-failover", true);
channel.basicConsume("my-queue", false, args, consumer);
这将创建一个带有设置参数的新消费者。
非同步镜像
节点可以随时加入集群。 依赖于队列的配置,当节点加入集群时,队列可能在新的节点上添加一个镜像。 在此阶段,新的镜像将为空:它不包含队列的任何现有内容。 此类镜像将接收发布到队列的新消息,因此随着时间的推移,它将准确地表示镜像队列的尾部。 当消息从镜像队列中耗尽时,新的镜像缺少消息的队列头部的尺寸将缩小,直到最终镜像的内容与领导者的内容完全匹配。 在此阶段,镜像可以被认为是完全同步的,但重要的是要注意,这是由于客户端在耗尽队列的预先存在的头部方面的操作造成的。
除非队列已明确同步,否则新添加的镜像不会提供任何额外的形式的冗余或队列内容的可用性,这些内容在镜像添加之前就已经存在。 由于在显式同步发生时队列会变得无响应,因此最好允许正在耗尽消息的活动队列自然同步,并且只显式同步非活动队列。
启用自动队列镜像时,请考虑所涉及队列的预期磁盘数据集。 具有大量数据集(例如,数十 GB 或更多)的队列必须将其复制到新添加的镜像(s),这会对集群资源(如网络带宽和磁盘 I/O)造成重大负载。 例如,这对于延迟队列来说是一种常见情况。
要查看镜像状态(它们是否同步),请使用
# mirror_pids is a new field alias introduced in RabbitMQ 3.11.4
rabbitmqctl list_queues name mirror_pids synchronised_mirror_pids
可以手动同步队列
rabbitmqctl sync_queue {name}
或者取消正在进行的同步
rabbitmqctl cancel_sync_queue {name}
这些功能也可以通过管理插件获得。
失效转移时非同步镜像的提升
默认情况下,如果队列的领导节点失效、失去与对等节点的连接或从集群中移除,最旧的镜像将被提升为新的领导者。 在某些情况下,此镜像可能是非同步的,这将导致数据丢失。
从 RabbitMQ 3.7.5 开始,ha-promote-on-failure
策略键控制是否允许非同步镜像提升。 当设置为when-synced
时,它将确保非同步镜像不会被提升。
默认值为always
。 when-synced
值应谨慎使用。 它以非同步镜像提升的安全为代价,增加了对队列领导者可用性的依赖。 有时,队列可用性可能比一致性更重要。
when-synced
提升策略避免了由于提升非同步镜像而导致的数据丢失,但使队列可用性依赖于其领导者的可用性。 如果队列领导节点失效,队列将变得不可用,直到队列领导者恢复。 如果队列领导者永久丢失,队列将不可用,除非它被删除并重新声明。 删除队列会删除其所有内容,这意味着使用此提升策略永久丢失领导者等同于丢失所有队列内容。
使用when-synced
提升策略的系统必须使用发布者确认,以便检测队列不可用和代理无法排队消息。
停止节点和同步
如果您停止包含镜像队列领导者的 RabbitMQ 节点,某些其他节点上的某个镜像将被提升为领导者(假设存在同步镜像;请参阅下面)。 如果您继续停止节点,您将到达镜像队列不再有镜像的点:它只存在于一个节点上,现在是它的领导者。 如果镜像队列被声明为持久,那么,如果它的最后一个剩余节点被关闭,队列中的持久消息将在该节点重新启动后存活下来。 通常,当您重新启动其他节点时,如果它们以前是镜像队列的一部分,那么它们将重新加入镜像队列。
但是,目前镜像无法知道其队列内容是否与它重新加入的领导者产生了分歧(例如,这可能发生在网络分区期间)。 因此,当镜像重新加入镜像队列时,它会丢弃它已经拥有的任何持久本地内容并从空开始。 在此阶段,它的行为与新节点加入集群时相同。
停止托管仅有非同步镜像的队列领导者的节点
当您关闭领导节点时,所有可用的镜像都可能是非同步的。 这可能发生的一种常见情况是滚动集群升级。
默认情况下,RabbitMQ 拒绝在受控领导者关闭(即显式停止 RabbitMQ 服务或关闭操作系统)时提升非同步镜像,以避免消息丢失;相反,整个队列将关闭,就好像不存在非同步镜像一样。
不受控制的领导者关闭(即服务器或节点崩溃或网络中断)仍将触发非同步镜像的提升。
如果您希望队列领导者在任何情况下都移动到非同步镜像(即,您选择队列的可用性而不是避免由于非同步镜像提升而导致的消息丢失),那么将ha-promote-on-shutdown
策略键设置为always
,而不是其默认值when-synced
。
如果ha-promote-on-failure
策略键设置为when-synced
,即使ha-promote-on-shutdown
键设置为always
,也不会提升非同步镜像。 这意味着,如果队列领导节点失效,队列将变得不可用,直到领导者恢复。 如果队列领导者永久丢失,队列将不可用,除非它被删除(这也将删除其所有内容)并重新声明。
请注意,ha-promote-on-shutdown
和ha-promote-on-failure
具有不同的默认行为。 ha-promote-on-shutdown
默认设置为when-synced
,而ha-promote-on-failure
默认设置为always
。
所有镜像都停止时领导者丢失
所有队列镜像都关闭时,可能丢失队列的领导者。 在正常操作中,队列的最后一个关闭节点将成为领导者,我们希望该节点在重新启动时仍然是领导者(因为它可能接收到了其他镜像没有看到的消息)。
但是,当您调用rabbitmqctl forget_cluster_node
时,RabbitMQ 将尝试为每个队列找到一个当前已停止的镜像,该队列的领导者位于我们正在忘记的节点上,并在该镜像再次启动时将其“提升”为新的领导者。 如果有多个候选人,则将选择最近停止的镜像。
重要的是要了解 RabbitMQ 只能在forget_cluster_node
期间提升停止的镜像,因为任何再次启动的镜像都会清除其内容,如上面"停止节点和同步"中所述。 因此,在删除停止集群中的丢失领导者时,您必须在再次启动镜像之前调用rabbitmqctl forget_cluster_node
。
批量同步
经典队列领导者以批次执行同步。 可以通过ha-sync-batch-size
队列参数配置批次。 如果未设置任何值,则mirroring_sync_batch_size
将用作默认值。 较早版本(早于 3.6.0)默认情况下将一次同步1
条消息。 通过以批次同步消息,可以显着加快同步过程。
要为ha-sync-batch-size
选择合适的值,您需要考虑
- 平均消息大小
- RabbitMQ 节点之间的网络吞吐量
net_ticktime
值
例如,如果您将ha-sync-batch-size
设置为50000
条消息,并且队列中的每条消息为 1KB,那么节点之间的每个同步消息将约为 49MB。 您需要确保队列镜像之间的网络能够适应这种类型的流量。 如果网络发送一批消息的时间超过net_ticktime,那么集群中的节点可能会认为它们处于网络分区的环境中。
通过设置参数mirroring_sync_max_throughput
,也可以控制通过网络发送的数据量。 该参数指定每秒传输的字节数。 默认值为0
,这将禁用此功能。
配置同步
让我们从队列同步最重要的方面开始:当队列正在同步时,所有其他队列操作都将被阻止。 依赖于多个因素,队列可能因同步而被阻止数分钟或数小时,在极端情况下甚至数天。
队列同步可以按如下方式配置
ha-sync-mode: manual
:这是默认模式。 新的队列镜像不会接收现有消息,它只会接收新消息。 一旦消费者耗尽了仅存在于领导者上的消息,新的队列镜像将随着时间的推移成为领导者的精确副本。 如果领导者队列在所有未同步消息耗尽之前失效,那么这些消息将丢失。 您可以手动完全同步队列,请参阅非同步镜像部分以了解详细信息。ha-sync-mode: automatic
:当新的镜像加入时,队列将自动同步。 值得重申的是,队列同步是一个阻塞操作。 如果队列很小,或者您在 RabbitMQ 节点之间拥有快速网络,并且ha-sync-batch-size
已优化,那么这是一个不错的选择。