若是RabbitMQ集群中只有一个Broker节点,那么该节点的失效将致使总体服务的临时性不可用,而且也可能会致使消息的丢失。能够将全部的消息都设置为持久化,而且对应的队列也能够将durable属性设置为true,可是这样仍然没法避免因为缓存的问题:由于在消息发送后和被写入磁盘并执行刷盘动做之间存在一个短暂却会产生问题的时间窗。经过publisher confirm机制可以确保客户端知道哪些消息已经存入磁盘,尽管如此,通常不但愿遇到单点故障致使服务不可用。node
若是RabbitMQ集群是由多个Broker节点组成的,那么从服务的总体性可用性上来说,该集群对于单点故障是由弹性的,可是也要注意:尽管交换器和绑定关系可以在单点故障问题上幸免于难,可是队列和其上的存储的消息却不行,这是由于队列进程及其内容仅仅维持在单个节点上,因此一个节点的失效表现为其对应的队列不可用。缓存
引入镜像队列机制,能够将队列镜像到集群中的其余Broker节点之上,若是集群中的一个节点失效了,队列可以自动的切换到镜像中的另外一个节点上以保证服务的可用性。在一般的用法中,针对每个配置镜像的队列都包含一个主节点(master)和若干个从节点(slave)。app
slave会准确的按照master的执行命令的顺序进行动做,故slave与master上维护的状态应该是相同的。若是master因为某种缘由失效,那么“资历最老”的slave会被提高为新的master。根据slave加入的时间排序,时间最长的slave即为“资历最老”。发送到镜像队列的全部消息会被同时发往master和全部的slave上,若是此时master挂掉了,消息还会在slave上,这样slave提高为master的时候消息也不会丢失。除发送消息(Basic.publish)外的全部动做都只会向master发送,而后再由master将命令执行的结果广播给各个slave。负载均衡
若是消费者与slave创建链接并进行订阅消费,其实质上都是从master上获取消息,只不过看似是从slave上消费而已。好比消费者与slave创建了TCP链接以后执行一个Basic.Get操做,那么首先是由slave将Basic.Get请求发往master,再由master准备好数据返回给slave,最后由slave投递给消费者。这里可能有疑问?大多数的读写压力都落到了master上,那么这样是否负载作不到有效的均衡?或者说是否能够像MySQL同样可以实现master写而slave读呢?注意这里的master和slave是针对队列而言的,而队列能够均匀的散落在集群的各个Broker节点以达到负载均衡的目的,由于真正的负载仍是针对实际的物理机器而言的,而不是内存中驻留的队列进程。spa
注意要点:操作系统
RabbitMQ的镜像队列同时支持publisher confirm和事务两种机制。在事务机制中,只有当前事务在所有的镜像中执行以后,客户端才会收到Tx.Commit-Ok的消息。一样的,在publisher confirm机制中,生产者进行当前消息确认的前提是该消息被所有进行所接收了。code
不一样于普通的非镜像队列,镜像队列的backing_queue比较特殊,其实现并不是是rabbit_variable_queue,它内部包裹了普通backing_queue进行本地消息的持久化处理,在此基础上增长了将消息和ack复制到全部镜像的功能。镜像队列的结构能够参考下图:blog
能够看出:master的backing_queue采用的是rabbit_mirror_queue_master,而slave的backing_queue实现是rabbit_mirror_queue_slave。排序
全部对rabbit_mirror_queue_master的操做都会经过组播GM(Guaranteed Multicast)的方式同步到各个slave中。GM负责消息的广播,rabbit_mirror_queue_slave负责回调处理,而master上的回调处理是由coordinator负责完成的,master对消息进行处理的同时将消息的处理经过GM广播给全部的slave,slave的GM收到消息后,经过回调交由rabbit_mirror_queue_slave进行实际的处理。接口
GM模块实现的是一种可靠的组播通讯协议,该协议可以保证组播消息的原子性,即保证组中活着的节点要么收到消息要么都收不到,它的实现大体为:将全部的节点造成一个循环链表,每一个节点都会监控位于本身左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管以保证本次广播的消息复制到全部节点。在master和slave上的这些GM造成一个组,这个组的信息会记录在Mnesia中,不一样的镜像造成不一样的组。操做命令从master对应的GM发出后,顺着链表传送到全部的节点。因为全部的节点组成了一个循环链表,master对应的GM最终会收到本身发出的操做命令,这个时候master就知道该操做命令都同步到了全部的slave上。
注意:每当一个节点加入或者从新加入到这个镜像链路中时,以前队列保存的内容会被所有清空。
当slave挂掉之后,除了与slave相连的客户端所有断开,没有其余影响。当master挂掉以后,会有如下连锁反应:
(1)与master链接的客户端链接所有断开;
(2)选举最老的slave做为新的master,由于最老的slave与旧的master之间的同步状态应该是最好的。若是此时全部的slave处于未同步状态,则未同步的消息会丢失;
(3)新的master从新入队全部unack消息,由于新的slave没法区分这些unack的消息是否已经到达客户端,或者是ack信息丢失在老的master链路上,再或者是丢失在老的master组播ack消息到全部slave的链路上,因此出于消息可靠性的考虑,从新入队全部的unack消息,不过此时客户端可能会有重复消息;
(4)若是客户端连着slave,而且Basic.Consume消费时指定了x-cancel-on-ha-failover参数,那么断开之时客户端会收到一个Consumer Cancellation Notification的通知,消费者客户端中会调用Consumer接口的handleCancel方法。若是未指定x-cancel-on-ha-failover参数,那么消费者将没法感知master宕机;
x-cancel-on-ha-failover参数的使用示例以下:
Channel channel = ...; Consumer consumer = ...; Map<String,Object> args = new HashMap<String,Object>(); args.put("x-cancel-on-ha-failover",true); channel.basicConsume("my-queue",false,args,consumer);
镜像队列的配置主要是经过Policy来完成的,主要详细介绍rabbitmqctl set_policy [-p vhost] [--priority priority] [--apply-to apply-to] {name} {pattern} {definition} 命令中的definition部分,对应镜像队列的配置来讲,definition中包含3个部分:ha-mode、ha-params和ha-sync-mode。
❤ ha-mode:指明镜像队列的模式,有效值为all、exactly、nodes。默认为all。all表示在集群中全部的节点上进行镜像;exactly表示在指定个数的节点上进行镜像,节点个数由ha-params指定;nodes表示在指定节点上进行镜像,节点名称经过ha-params指定,节点的名称一般相似于rabbit@hostname,能够经过rabbitmqctl cluster_status命令查看到。
❤ ha-params:不一样的ha-modee配置中须要用到的参数;
❤ ha-sync-mode:队列中消息的同步方式,有效值为automatic何manual;
ha-mode参数对排他队列并不生效,由于排他队列是链接独占的,当链接断开时队列会自动删除,因此实际上这个参数对排他队列没有任何意义。
将新节点加入到已存在的镜像队列中时,默认状况下ha-sync-mode取值为manual,镜像队列中的消息不会主动同步到新的slave中,除非显示的调用同步命令。当调用同步命令后,队列开始阻塞,没法对其进行其余操做,直到同步完成。当ha-sync-mode设置为automatic时,新加入的slave会默认同步已知的镜像队列。因为同步过程的限制,因此不建议对生成环境中正在使用的队列进行操做。使用rabbitmqctl list_queues {name} slave_pids synchronised_slave_pids命令能够查看哪些slaves已经同步完成。经过手动方式同步一个队列的命令为rabbitmqctl sync_queue {name} ,一样取消某个队列的同步操做:rabbitmqctl cancel_sync_queue {name}。
当全部slave都出现未同步状态,而且ha-promote-on-shutdown设置为when-synced(默认)时,若是master由于主动缘由停掉,好比经过rabbitmqctl stop命令或者优雅关闭操做系统,那么slave不会接管master,也就是此时镜像队列不可用;可是若是master由于被动缘由停掉,好比Erlang虚拟机或者操做系统崩溃,那么slave会接管master。这个配置项隐含的价值取向是保证消息可靠不丢失,同时放弃了可用性。若是ha-promote-on-shutdown设置为always,那么不论master以何种缘由中止,slave都会接管master,优先保证可用性,不过消息可能会丢失。
镜像队列中最后一个中止的节点会是master,启动顺序必须是master先启动。若是slave先启动,它会有30秒的等待时间,等待master的启动,而后加入的=到集群中。若是30秒内master没有启动,slave会自动中止。当全部节点因故(断电)同时离线时,每一个节点都认为本身不是最后中止的节点,要恢复镜像队列,能够尝试在30秒内启动全部节点。
参考:《RabbitMQ实战指南》 朱忠华 编著;