RabbitMQ 获得 HA 升级
这是关于仲裁队列(quorum queues)系列的第一部分,仲裁队列是我们新的复制队列类型。我们将涵盖所有内容,从仲裁队列是什么,到硬件要求,从镜像队列迁移以及最佳实践。
仲裁队列简介
镜像队列,也称为 HA 队列,多年来一直是需要为您的消息提供额外数据安全保证的事实标准选项。仲裁队列是下一代复制队列,旨在取代镜像队列的大多数用例,并且从 3.8 版本及更高版本开始提供。
在本博客系列中,我们将介绍以下内容
- 第一部分 - 了解对新型复制队列的需求以及它的工作原理(本文)
- 第二部分 - 为什么您对存储驱动器的选择对于仲裁队列很重要
- 第三部分 - 仲裁队列和流量控制。概念。
- 第四部分 - 仲裁队列和流量控制。单队列基准测试。
- 第五部分 - 仲裁队列和流量控制。压力测试。
- 第六部分 - 仲裁队列和集群/VM 大小调整 (即将推出)
- 第七部分 - 何时以及如何从镜像队列迁移到仲裁队列 (即将推出)
- 第八部分 - 最佳实践和注意事项 (即将推出)
- 第九部分 - 监控和管理操作 (即将推出)
为什么要提供新型复制队列?
镜像队列基于一种名为链式复制的复制算法。在队列的上下文中,链式复制形成一个队列环,其中有一个领导者(master)和一个或多个辅助队列(mirrors)。所有消息都发布到领导者并从领导者处消费,然后领导者将这些操作复制到其相邻的镜像,镜像又复制到其相邻的镜像。这样一直持续到到达最后一个镜像,然后最后一个镜像通知主节点操作已完全复制。

由于某些导致消息丢失的边缘情况,此算法被修改,以便通道进程还会将消息直接发送到每个镜像。不幸的是,这意味着从发布者接收消息的 broker 必须将每条消息发送两次,这使网络负载加倍。

关于这种复制算法,还存在一些痛点,这些痛点集中在它如何处理由于服务器重启、节点故障或网络分区而离开环的镜像。
当镜像与对等节点失去通信超过一定时间限制时,它将从环中移除,并且队列继续可用。问题发生在镜像重新加入环时。首先,镜像丢弃其所有数据,然后,可以选择性地开始一个称为同步的过程。
同步是主节点将其当前消息复制到镜像的过程。这是一个停止世界的过程,队列将被冻结,直到同步完成。如果队列非常大,这将成为一个问题,因为不可用时间可能会很长。

另一种选择是不将重新加入的镜像与主节点同步。在这种情况下,我们最终会降低冗余,但避免了可能痛苦的同步。当然,如果队列为空或只有少量消息,那么同步不会构成大问题。
另一个重要主题是它如何处理网络分区。当发生将集群分成两半的分区时,我们将最终得到一个或多个与主节点失去通信的镜像。作为管理员,我们可以在此时选择可用性或一致性。cluster_partition_handling 配置决定了 RabbitMQ 如何处理分区。
如果我们不想丢失消息,那么我们将配置集群以使用 pause-minority 模式。这基本上会停止分区少数方上的所有 broker。在多数方(如果存在),队列继续运行,只是冗余降低。一旦分区解决,集群将恢复正常。此策略选择一致性(尽管冗余较少)而不是可用性。

如果我们希望分区两侧都保持可用性,那么我们可以选择 ignore 或 auto-heal 模式。这将允许将镜像提升为主节点,这意味着我们在两侧都有一个主节点。这允许队列继续接收和传递消息,无论客户端连接到分区的哪一侧。不幸的是,在解决分区时,分区的一侧被选为受害者并重新启动,丢失该侧队列中的任何消息。这不是一个理想的选择,但在某些情况下,它仍然比变得不可用更适合您的需求。

我们需要一种更好的复制算法,这就是仲裁队列诞生的原因。
仲裁队列
仲裁队列不使用链式复制,而是基于成熟且经过数学证明的 Raft 协议。Raft 是一种共识算法,用于在节点集群中复制操作日志。它需要参与节点中的法定人数(多数)可用,并就附加到分布式日志的每个新操作达成一致。这就是仲裁队列名称的由来。
队列有哪些操作?我们有入队操作和消费者确认操作。就像镜像队列一样,所有客户端都与领导者交互,领导者的工作是将入队和确认复制到其追随者。

该算法更有效,并且可以实现比镜像队列更高的吞吐量。它确实具有更高的端到端延迟,并且这些延迟与您的磁盘的吞吐量/延迟密切相关。仲裁队列仅在消息写入大多数节点的磁盘后才确认消息,因此磁盘性能在仲裁队列性能中起着重要作用。
无需艰难的抉择
没有停止世界同步,没有在重新加入时丢弃数据,完全无需就自动与手动同步做出艰难的决定。无需在可用性与一致性之间做出选择;仲裁队列仅在消息复制到大多数节点后才会确认消息。如果大多数节点宕机,那么您将失去可用性。
网络分区
在处理网络分区时,仲裁队列要简单得多。首先,它们使用一个单独且速度更快的故障检测器,可以快速检测分区并触发快速领导者选举,这意味着可用性要么不受影响,要么可以快速恢复。cluster_partition_handling 配置不适用于仲裁队列,尽管 pause_minority 模式仍然会影响仲裁队列,因为当少数方暂停时,托管在该节点上的任何仲裁队列领导者都将变得不可用。但是,由于故障检测器的速度,在发生分区的情况下,领导者选举应该在新领导者在此暂停触发之前就已选出。
仲裁队列适用于数据安全至上的场景。但请记住,数据安全始于应用程序做正确的事情,正确使用发布者确认和消费者确认。仲裁队列确实有一些限制和注意事项,我们将在本系列的后面部分进行介绍。
x-queue-type:classic 和 quorum
现在我们有了一种新型队列,我们将队列称为 classic 队列或 quorum 队列。经典队列与以前的旧队列相同。使用 ha-mode、ha-params、ha-sync-mode 属性声明经典队列会使其成为经典镜像队列,使用与队列匹配的这些参数定义策略也会如此。
但是,您无法通过策略将经典队列转换为仲裁队列。设置为 quorum 的 x-queue-type 属性必须包含在队列声明中。它从一开始就是仲裁队列。
管理 UI 中的仲裁队列
在下面的屏幕截图中,首先您会注意到 x-queue-type: quorum 参数和 x-quorum-initial-group-size: 3 参数,它们表明此队列是一个复制因子为 3 的仲裁队列(一个领导者和两个追随者)。
图 7. 在管理 UI 中看到的仲裁队列我们还可以看到队列的成员、哪些成员在线以及当前的领导者是谁。
声明仲裁队列与声明任何其他队列一样简单,只需使用 x-queue-type 参数。默认情况下,仲裁大小(复制因子)为 5,但对于较小的集群,大小将是集群本身的大小。
在管理 UI 中,您将看到添加经典队列或仲裁队列的选项。

当您选择仲裁队列时,您将看到可用参数发生变化。

仲裁队列有三个新的可用参数。其中两个与内存中保留多少消息有关。默认情况下,所有消息都保存在内存中,因此如果仲裁队列的长度增加,则可能会给集群带来内存压力。我们可以通过设置以下一个或两个参数来限制消息使用的内存量
- x-max-in-memory-length
- x-max-in-memory-bytes
但是,队列索引仍然存储在内存中,因此,持续增长的队列仍然会对集群造成内存压力。我们将在本系列的后面部分更仔细地研究这一点。
新的死信消息功能仅适用于仲裁队列,并使用 x-delivery-limit 参数进行设置。每次消息重新传递给消费者时,计数器都会递增。一旦重新传递计数超过 x-delivery-limit,消息将被丢弃或死信化(如果已配置 DLX 交换机)。
总结
仲裁队列提供万无一失的安全保证,并且在重新启动服务器时减少了麻烦。与镜像队列相比,它们还会对您的硬件施加不同的压力,因此在您进行跳转之前,请查看我们的下一篇文章,其中包含有关迁移和所需硬件的指南。
在下一篇文章中,我们将介绍为什么我们推荐 SSD 而不是 HDD,以及我们使用每种存储驱动器类型获得的不同的性能特征。