RabbitMQ 3.8 Feature Focus - Quorum Queues
标题:RabbitMQ 3.8 Feature Focus - Quorum Queues
原文:https://www.cloudamqp.com/blog/rabbitmq-quorum-queues.html
时间:2019-03-28
RabbitMQ 3.8将于今年推出,它将带来四个新的主要特性。也许最重要的是一种称为仲裁队列的新队列类型,它是一种复制队列,可提供高可用性和数据安全性。其思想是跨多个服务器复制队列,以便在服务器崩溃或关闭时,队列继续可用且不会丢失消息。
RabbitMQ已经有了一个用于此目的的现有解决方案,称为镜像队列或HA队列。多年来,镜像队列一直是获得高可用性和数据冗余的事实上的方式,但该功能存在一些严重的设计缺陷,这使得它不是一个理想的选择。
What is wrong with Mirrored Queues anyway?
主要问题围绕同步模型和性能。由于消息是使用效率非常低的算法复制的,因此性能较差。HA队列同步是一个麻烦的话题,RabbitMQ管理员对此感到恐惧。
镜像队列的工作方式是有一个leader队列和一个或多个镜像队列。所有读和写操作都会通过leader队列,然后leader将所有命令(write、read、ack、nack等)复制到镜像队列。所有存活的镜像队列收到消息后,leader将向发布者发送确认消息。此时,如果leader失败,一个镜像将升级为leader,队列将保持可用,而不会丢失数据。

当有多个镜像队列时,leader和镜像将分布在集群中,因此每个broker都可以充当多个leader和镜像。

镜像队列最主要的问题在于,当broker离线恢复时,镜像队列中的任何数据都会被丢弃。这是致命的设计缺陷#1。现在镜像已恢复在线状态但队列为空,管理员需要做出一个决定:是否同步镜像。同步是指将当前消息从leader复制到镜像。
这就是致命设计缺陷#2的原因。同步数据是阻塞,导致整个队列不可用。通常情况下,如果一切正常,队列应该是空的,或者队列中应该有少量消息。这通常是健康状态。消息以相同的速度发布和消费,消息在队列中停留的时间很短。但有时队列可能会堆积,要么是因为下游系统运行缓慢或离线。在此期间,系统仍然可用,但在其队列中会累积消息。
如果没有消息或只有几千条小消息,那么同步的影响很小。同步将很快,发布者可以重新发送broker无法接受的任何消息。但当队列较大时,影响要大得多。同步可能需要几分钟、几小时甚至几天的时间(尽管在大多数情况下,当我们看到这一点时,服务器在完成同步之前会崩溃,然后需要反复启动)。不仅如此,同步还会导致集群上出现与内存相关的问题,这又会反过来导致同步受阻,最终需要重新启动服务。
因此,有时管理员只选择不同步镜像。所有新消息都将得到复制,但任何已存在的消息都不会,从而导致冗余减少,并使集群面临更大的消息丢失风险。
这些问题还导致滚动升级出现问题,因为重新启动的代理将丢弃其所有数据,并需要同步以恢复完整的数据冗余。
Quorum Queues - The next generation
仲裁队列旨在解决镜像队列的性能和同步失败问题。但它在第一个版本中减少了一系列功能,并引入了新问题。不幸的是,我们难以抉择。
仲裁队列使用Raft协议的一种变体,Raft协议已成为业界事实上的分布式共识算法。它比镜像队列更安全,并能达到更高的吞吐量。
Message Replication with Raft
每个仲裁队列都是一个复制队列;它有一个leader和多个follower。涉及leader和follower的常用术语是复制。复制因子为5的仲裁队列将由五个副本组成:leader和四个follower。每个副本将托管在不同的节点(broker)上。
客户端(发布者和消费者)始终与leader副本交互,leader副本随后将所有命令(写入、读取、确认等)复制到follower。Follower根本不与客户端交互;它们的存在只是为了冗余,允许在RabbitMQ broker出现故障、关闭或重新启动时保持服务可用。当一个broker离线时,另一个broker上的follower副本将被选为leader,服务将继续。

仲裁队列的名称,来源于所有操作(消息复制和领导者选举)都需要多数副本(称为仲裁)的同意。当发布者发送消息时,队列只能在大多数副本将消息写入磁盘后进行确认。这意味着速度较慢的少数节点不会使整个队列变慢。同样,只有在多数人同意的情况下,才能选出一位领导人,这使得在网络分区时不会出现两个领导者同时接收消息。因此,仲裁队列面向一致性而非可用性。
Quorum Queues - The Good Parts
FIRSTLY
客户端不需要更改发布和订阅的方式,队列类型与这些操作无关。唯一的区别是,在声明队列时,必须将其声明为仲裁队列。因此,如果您依赖客户端进行队列声明,则需要它来添加必要的属性。
SECONDLY
同步问题消失了。当broker重新上线时,他们不会丢弃自己的数据。所有消息都保留在磁盘上,领导者只需从其停止的位置复制消息。
将消息复制到返回集群的跟随者是非阻塞的。所以队列不会受到新跟随者或重新加入的跟随者的影响。唯一的影响可能是网络利用率。
这本身就使得消息比镜像队列更持久,因为不存在同步问题的风险。此外,由于每次写入都必须由大多数节点写入磁盘,因此不会出现导致消息丢失的脑裂情况。请注意,有时不可用意味着消息丢失:如果在队列不可用时,发布者没有采取措施(例如重试),只能消息会被丢弃,在RabbitMQ服务端之外导致消息丢失。
FINALLY
Raft比镜像队列算法效率更高,提供更好的吞吐量。
到目前为止,所有这些加起来可以实现提高吞吐量、更好的数据安全性、更容易的滚动服务升级(如操作系统补丁)。但让我们开始看看仲裁队列的缺点。
The Not So Shiny Parts
LESS FEATURES
某些功能在第一个版本中不可用,或者永远不可用。仲裁队列中不可用的功能列表:
- Non-durable messages
- Queue Exclusivity
- Queue/message TTL
- Some policies are not available. Only DLX and length limit are available.
- Priorities
- Lazy queues
- No global QoS
DISK USAGE - WRITE AMPLIFICATION
仲裁队列的磁盘和内存配置文件与普通队列不同。
普通队列有一个共享存储模型,其中一条消息只存储一次,而投递了这条消息的所有队列只是获取了对这条消息的引用。这意味着,在发布-订阅模型中,消息将被投递到多个队列的事实不会导致磁盘存储大小随队列数量线性增长。
假设有一个绑定了10个队列的fanout exchange:

下图是每一个队列都是复制因子为5的镜像队列。

对于发送到fanout exchange的每一条消息,最终会在集群中存在5条该消息,写放大比例为5。
另一方面,仲裁队列只在内存中有一个共享模型。在磁盘上,每条消息都是单独存储的,因此发布订阅会产生了写放大,这可能会使仲裁队列不可行,或者需要更大或更快的磁盘。
下图是每一个队列都是复制因子为5的仲裁队列。

对于发送到fanout exchange的每一条消息,最终会在集群中存在50条该消息,写放大比例为50。
因此,fanout exchange不太适合仲裁队列,大规模扇出可能根本不可能。
MEMORY USAGE - ALL MESSAGES IN-MEMORY ALL THE TIME
仲裁队列中的所有消息始终在内存中这一事实也会增加内存使用率,最终可能导致集群不可用。不断增长的队列可能会导致生产消息被阻塞,直到消息被消费并从内存中删除。这就是为什么在使用仲裁队列时,使用长度限制策略至关重要的原因,通过死信exchange将消息卸载到所谓的lazy queue中。
RabbitMQ 3.10之后支持Lazy Mode。
这使得规划和监控变得更加重要。下游停机或减速可能会导致多个队列增长,您需要相应地进行规划。有多少仲裁队列,预期的生产速度是多少,如果集群达到其内存限制,还会影响哪些其他队列?
PERMANENT LOSS OF A MAJORITY = LOST QUEUE
如果超过半数副本永久丢失数据,那么这些数据将永远消失,即使少数派节点保存了该数据,队列也无法恢复,必须强制删除。这是一种不太可能的情况,但风险是存在的。因此建议使用可靠的磁盘,复制系数最好为3或5。
LATENCY
虽然吞吐量更好,但延迟可能更高。这归结为对Raft的使用。我们不会得到非持久性消息,所有消息始终跨所有副本持久化到磁盘。安全性是仲裁队列的主要目标。
Only the Beginning
仲裁队列目前仍处于测试阶段,但今年晚些时候它们将包含在3.8版本中,可供生产环境使用。您现在可以开始使用测试版了,它非常稳定。您可以在GitHub上找到最新的3.7和3.8版本。
仲裁队列的第一个版本旨在实现最小的功能,重点关注可靠性和性能。RabbitMQ团队计划改进许多方面,包括内存使用率。因此,尽管仲裁队列无论如何都不是万能的,但对于某些使用场景,仲裁队列成为了替代镜像队列的更好方案。关于仲裁队列的更多信息可以参考官方文档。