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