[TOC]
高可用队列系统
可靠性的递增:
持久化的 exchange / queue
持久化的 message
投递成功确认机制 confirm
消费成功确认机制 ack
消除单点故障 集群镜像队列
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。此时会发生如下的情形:
被选中作为新的master的slave通常是最老的那个,因为最老的slave与前任master之间的同步状态应该是最好的。然而,需要注意的是,如果存在没有任何一个slave与master完全同步的情况,那么前任master中未被同步的消息将会丢失。
slave节点认为目前所有的消费者都已经突然的disconnect了。它会requeue所有被发送到客户端但没有被ack的消息。这里包括客户端以及发送ack但是丢失在返回broker的路上,或者master已经收到但是其他的slave没有收到,这些消息都会被requeue。无论何种清理,新的master只能requeue所有没有被ack的消息。
消费端如果引入了Consumer Cancellation Notification,那么当当前的queue挂掉的时候应该被通知到。
由于requeue的存在,客户端当重新消费queue的时候,有可能将之前消费过的消息又顺序的消费一遍。
当一个slave提升为master的时候,发送到当前镜像队列的消息将不会丢失(除非这个新的master紧接着挂了)。消息发送到一个slave的时候,将会被路由到master上,进而又被复制到所有的slave上。此时如果master挂了,消息将会继续发送到slave上,当一个slave提升为master的时候,这些消息会被存入queue中。
客户端如果在发送消息是采用了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同步的状态,因此每当一个节点加入或者重新加入(例如从网络分区中恢复过来)镜像队列,之前保存的队列内容会被清空。