目录

[TOC]

高可用队列系统

可靠性的递增:

  1. 持久化的 exchange / queue

  2. 持久化的 message

  3. 投递成功确认机制 confirm

  4. 消费成功确认机制 ack

  5. 消除单点故障 集群镜像队列

1. 概述

每个镜像队列包含一个 master 节点和任意个 slave 节点。客户端连接的一定是 master 节点,而不是slave节点(就算程序连接的是slave节点,也会路由到master节点进行操作),master执行命令后会顺序地同步到slave,故slave与master上维护的状态应该是相同的。

  • 发送的消息将被复制到队列的所有镜像节点

  • 被消费并被确认的消息将会被清除

如果master节点由于某种原因失效,那么 “资历最老” 的 slave 节点将被提升为新的 master节点。

2. 策略配置

$ rabbitmqctl set_policy [-p Vhost] 策略名 匹配的队列 Definition [Priority]  # 格式

# 1 master + 2 slave 所有队列
$ rabbitmqctl set_policy ha-3-mirrors ".*" '{"ha-mode":"exactly", "ha-params":3, "ha-sync-mode":"manual"}'

$ rabbitmqctl list_policies # 列出所有策略
$ rabbitmqctl clear_policy <policyname> # 删除策略
$ rabbitmqctl list_queues name slave_pids synchronised_slave_pids # 查看那些 slaves 已经完成同步
$ rabbitmqctl sync_queue name          # 通过手动的方式同步一个 queue:
$ rabbitmqctl cancel_sync_queue name # 取消某个 queue 的同步功能:

2.1 ha-mode 镜像策略

取值如下:

  • all 表示所有节点都保存一份备份

  • exactly 指定镜像备份个数

  • nodes 需要手动指定镜像节点

综合来看,all 和 nodes 都是不可取的.所以建议采用 exactly 模式,配置 1 master + 2 slave ,共三个备份的策略配置镜像队列.

2.1 master节点位置

在 policy 上设置 queue-master-locator :

  • min-masters:选择 master 数最少的那个服务节点(选这个)

  • client-local:选择与client相连接的那个服务节点

  • random:随机分配

3. 新增节点

当新节点加入进来的时候,某queue有可能在这个新的节点上添加一个镜像,此时这个slave镜像是空的,它不包含任何queue中已经存在的内容。新加入的镜像可以收到生产者新发送过来的消息,其内容与其他镜像的尾部保持一致。随着queue中的消息被逐渐的消费,新加入的镜像中“没有”的消息逐渐减少,直到与其他镜像保持一致,既而就已经完全处于同步状态。但是需要注意的是,上述的同步行为是基于客户端的操作而触发的。

ha-sync-mode=manual 镜像队列中的消息不会主动同步到新节点,除非显式调用同步命令。

ha-sync-mode=automatic 新加入节点时会默认同步已知的镜像队列。

这么说,我们设置为 automatic 是否就完美了呢?

恰恰相反,队列在同步过程中,会开始阻塞,无法对其进行操作,直到同步完毕 .所以在生产环境选择 manual 模式,系统会更稳定.

4. 节点崩溃

4.1 salve崩溃

如果某个slave失效了,系统处理做些记录外几乎啥都不做:master 依旧是 master,客户端不需要采取任何行动或者被通知slave已失效。注意slave的失效不会被立刻检测出来.

4.2 master崩溃

如果master失效了,那么slave中的一个必须被选中为master。此时会发生如下的情形:

  1. 被选中作为新的master的slave通常是最老的那个,因为最老的slave与前任master之间的同步状态应该是最好的。然而,需要注意的是,如果存在没有任何一个slave与master完全同步的情况,那么前任master中未被同步的消息将会丢失。

  2. slave节点认为目前所有的消费者都已经突然的disconnect了。它会requeue所有被发送到客户端但没有被ack的消息。这里包括客户端以及发送ack但是丢失在返回broker的路上,或者master已经收到但是其他的slave没有收到,这些消息都会被requeue。无论何种清理,新的master只能requeue所有没有被ack的消息。

  3. 消费端如果引入了Consumer Cancellation Notification,那么当当前的queue挂掉的时候应该被通知到。

  4. 由于requeue的存在,客户端当重新消费queue的时候,有可能将之前消费过的消息又顺序的消费一遍。

  5. 当一个slave提升为master的时候,发送到当前镜像队列的消息将不会丢失(除非这个新的master紧接着挂了)。消息发送到一个slave的时候,将会被路由到master上,进而又被复制到所有的slave上。此时如果master挂了,消息将会继续发送到slave上,当一个slave提升为master的时候,这些消息会被存入queue中。

  6. 客户端如果在发送消息是采用了publisher confirm机制,那么在消息发送和消息确认之间master挂掉(或者任何slave挂掉)都不会影响confirm机制的正确运行。

如果你在消费一个镜像队列的时候这是autoAck=true(客户端不会进行消息确认),那么消息有可能会丢失。broker中的消息一旦发送出去就会被立刻确认(被确认的消息不能再被消费,且broker内部线程会执行清理工作将此消息清除),如果与客户端建立的连接突然中断,那么消息将会永远丢失。所以为了确保消息不丢失,还是建议你在消费时将autoAck设置为false。

4.3 salve 顶替 master

ha-promote-on-shutdown=when-synced :

  • 如果master因为主动的原因停掉,比如是通过 rabbitmqctl stop 命令停止或者优雅关闭 OS ,那么 slave 不会接管 master,也就是此时镜像队列 不可用

  • 如果master因为被动原因停掉,比如 VM 或者 OS crash了,那么slave会接管master。

这个配置项隐含的价值取向是,保证已经收到的消息不丢失,放弃可用性。

ha-promote-on-shutdown=always:

  • 那么不论 master 因为何种原因停止,slave都会接管master,优先保证可用性

试想一下,如果 ha-mode 设置为exactly,ha-params设置为2,当其中一个镜像节点A挂掉,那么在集群中的另一个节点B将会被设置为镜像,B尚未与master同步,此时master节点也挂掉,那么这个B镜像将被提升为master,原master中的数据就丢失了。(当所有 slave 与 master 未同步状态)

4.4 崩溃时消费者表现

x-cancel-on-ha-failover=true

那么当在故障处理的时候将会停止消费,并且会受到一个 consumer cancellation notification . 这样消费需要重新发送 Basic.Consume 进而可以重新消费。

5. 关闭整个集群

如果你关闭了镜像队列中的master节点,那么剩余的镜像中会选举一个作为新的master节点(假设都处于同步的状态)。如果你继续关闭节点直到没有多余镜像了,那么此时只有一个节点可用,这个节点也是master节点。如果这个镜像队列配置了持久化属性(durable=true)。那么当最后的节点重启之后,消息不会丢失。然后你再重启其他的节点,它们会陆续的加入到镜像队列中来。

然而,目前还没有方法判断一个重新加入的镜像是否保持和master同步的状态,因此每当一个节点加入或者重新加入(例如从网络分区中恢复过来)镜像队列,之前保存的队列内容会被清空。