RabbitMQ中的高可用性:解决难题的一部分
在 RabbitMQ 2.6.0 中,我们引入了高可用性队列。这需要对 AMQP 进行新的扩展,以及大量的文档,但到目前为止,关于它们如何工作的信息很少。
高可用性(HA)是一个通常被过度使用的术语,对不同的人意味着不同的东西。在 RabbitMQ 的上下文中,高可用性有很多方面,其中一些方面这项工作解决了,而另一些则没有。它没有解决的问题包括
-
维护与 RabbitMQ 代理或节点的连接:使用某种 TCP 负载均衡器或代理是这里最好的方法,尽管其他解决方案(例如动态更新 DNS 条目或只是预先加载客户端要连接的地址列表)也可能同样有效。
-
故障恢复:如果客户端由于连接的节点故障而与代理断开连接,如果客户端是发布客户端,代理可能已经接受并传递了来自客户端的消息,而客户端没有收到确认;同样,在消费端,客户端可能已经为消息发出确认,并且不知道这些确认是否到达代理并在故障发生前被处理。简而言之,您仍然需要确保您的消费客户端能够识别和处理重复消息。
-
从网络分区或分割中自动修复。RabbitMQ 使用 Erlang 分布式数据库 Mnesia。此数据库本身无法处理网络分区:它非常重视一致性和可用性,而不是分区(来自CAP定理)。由于 RabbitMQ 依赖于 Mnesia,因此 RabbitMQ 本身具有相同的特性。因此,RabbitMQ 中的 HA 工作可以防止队列在节点故障时消失,但没有说明在修复失败的节点时如何自动重新加入:这仍然需要手动干预。
这些根本不是新问题;RabbitMQ 的 HA 工作并没有试图解决这些问题。相反,它只专注于防止队列绑定到集群中的单个节点。
之前的情况是队列仅存在于一个节点上。如果该节点发生故障,则队列将不可用。HA 工作通过在其他节点上镜像队列来解决此问题:在队列的主节点上发生的所有操作都会被拦截并按相同的顺序应用于镜像中的每个从节点。
这需要
-
能够拦截在队列上执行的所有操作。幸运的是,我们已经拥有的代码抽象使得这变得相当容易。
-
能够将这些操作可靠地、一致地且按顺序传达给镜像中的所有从节点。为此,我们编写了一个新的保证多播模块(也称为原子广播)。
-
能够可靠地检测到节点丢失,从而确保来自该节点的消息不会到达从节点的子集:为了确保镜像队列的成员彼此保持同步,在主节点发生故障时,主节点正在发送给从节点的任何消息要么完全失败,要么完全成功(这实际上是原子广播中的原子)。
此外,镜像成员之间所有这些通信都是以异步方式进行的。这具有优势,例如,如果其中一个从节点开始出现问题,它可以防止主节点变慢;但它也存在缺点,例如在主节点故障和从节点提升的情况下操作交错的复杂性。
一旦主节点确实发生故障,就会选择一个从节点进行提升。选择的从节点是最老的从节点,因为认为它最有可能包含与已失效主队列内容匹配的内容。这一点很重要,因为目前没有镜像队列的积极同步。因此,如果您创建一个镜像队列,向其中发送消息,然后添加另一个节点,然后该节点镜像该队列,则新节点上的从节点将不会收到现有消息。只有发布到队列的新消息才会发送到镜像队列的所有当前成员。因此,通过从队列中消费并因此处理队列头部的消息,将消除未完全镜像的消息。因此,通过提升最老的从节点,您可以最大限度地减少队列头部可能仅为主节点已知的消息数量。