MQ - RabbitMQ镜像队列机制
MQ - RabbitMQ镜像队列机制
镜像队列在解决什么?
- 镜像队列 (
Mirror Queue
) 机制,可以将队列镜像到集群中的其他Broker节点之上。当集群中的一个节点失效了,队列能自动地切换到镜像中的另一个节点上,以此可以保证服务的可用性,而不是节点实现其上的队列不可用。
1. 机制原理
一般情况下,给每一个配置镜像的队列,即镜像队列都包含一个主节点(master) 和若千个从节点 (slave)。如下图,给qingjun_queue
队列配置镜像包含了一个主节点和两个从节点。这里要注意镜像的主从节点和rabbitmq服务主从节点的区别。
实现原理:
- 镜像队列的slave节点会准确地按照镜像队列的master节点执行命令的顺序进行动作,所以镜像队列的master节点和slave节点数据是相同的,维护状态也应该是相同的。
- 当镜像队列的master节点失效,"资历最老"的镜像队列slave1就会被提升为新的master。选主机制是根据镜像队列save节点加入时间顺序来的,时间最长的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投递给消费者。
1.1. 集群结构
如下图,集群一共三个节点,broker1、broker2、broker3。每个broker节点都包含1个镜像队列的master和2个镜像队列的slave。
qingjun_queue
镜像队列的负载大多都集中在broker1上,baimu_queue
镜像队列的负载大多都集中在broker2上,wuhan_queue
镜像队列的负载大多都集中在broker3上。- 只要确保镜像队列的master节点均匀散落在集群中的各Broker节点上就可以确保很大程度上的负载均衡。
- 为什么不是绝对? 因为每个队列的流量会有不同,因此均匀散落各个队列的master也无法确保绝对的负载均衡)。
注意事项:
rabbitmq镜像队列同时支持发送方确认机制(publisher confirm
)和事务机制。
- 在事务机制中,只有当前事务在全部镜像中执行之后,客户端才会收到
Tx.Commit-ok
的消息。 - 在
publisher confirmm
机制中,生产者进行当前消息确认的前提是该消息被全部进行所接收了。
2. 镜像结构
镜像队列结构和普通队列结构不同点在于backing_queue的不同,普通队列使用的是rabbit_variable_queue
,而镜像队列使用的backing_queue内部包裹了普通backing_queue进行本地消息消息持久化处理,在此基础上增加了将消息和ack复制到所有镜像的功能。
如下图,master使用的backing_queue是rabbit_mirror_queue_master
,slave使用的backing_queue是rabbit_mirror_queue_slave
。
普通队列结构
镜像队列结构
2.1. 组播GM
GM的作用:
- 所有对
rabbit_mirror_queue_master
的操作都会通过组播 GM(GuaranteedMulticast
)的方式同步到各个slave中。 - GM 负责消息的广播,
rabbit_mirror_queue_slave
负责回调处理,而master上的回调处理是由coordinator负责完成的。 - 除了
Basic.Publish
,所有的操作都是通过 master 来完成的,master 对消息进行处理的同时将消息的处理通过GM广播给所有的slave,slave的GM收到消息后,通过回调交由rabbit_mirror_queue_slave进行实际的处理。
2.1.1. 实现原理
GM 模块是通过组播通信协议来实现,该协议能够保证组播消息的原子性。换言之,保证组中活着的节点要么都收到消息,要么都收不到。
实现过程:
将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点。
- 当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上。
- 当有节点失效时,相邻的节点会接管以保证本次广播的消息会复制到所有的节点。
master和slave上的组播GM会形成一个组 (gm group
),这个组的信息会记录在Mnesia中。
不同的镜像队列形成不同的组。操作命令从master对应的GM发出后,顺着链表传送到所有的节点。由于所有节点组成了一个循环链表,master 对应的 GM 最终会收到自己发送的操作命令,这个时候 master 就知道该操作命令都同步到了所有的 slave 上。
2.1.2. 加入新节点
如下图,新增了一个节点4,整个过程就像在链表中间插入一个节点。
注意事项:
每当个节点加入或者重新加入到这个镜像链路中时,之前队列保存的内容会被全部清空。
2.1.3. 节点宕机的影响
当slave挂掉之后,除了与slave相连的客户端连接全部断开,没有其他影响。
当master挂掉之后,会有以下连锁反应:
- 先与master连接的客户端连接全部断开。
- 选举最老的slave作为新的master。因为最老的slave与旧的master之间的同步状态是最好的。如果此时所有slave处于未同步状态,则未同步的消息会丢失。
- 新的master重新入队所有unack的消息。因为新的slave无法区分这些unack的消息是否已经到达客户端,或者是ack信息丢失在老的master链路上,再或者是丢失在老的master组播ack消息到所有slave的链路上,所以出于消息可靠性的考虑,重新入队所有unack的消息,带来的弊端就是客户端可能会有重复消息。
- 若客户端连接着slave,并且
Basic.Consume
消费时指定了x-cancel-on-ha:failover
参数,那么断开时客户端会收到一个Consumer Cancellation Notifcation
的通知,消费者客户端中会回调consumer接口的 handleCancel 方法。如果未指定x-cancel-on-ha-failover
参数,那么消费者将无法感知master宕机。
3. 配置镜像队列
镜像队列的配置主要是通过添加相应的Policy来完成的,两种方式,一是在代码里提前写好配置,二是使用管理命令配置。
注意事项:
- 镜像队列中最后一个停止的节点会是master,启动顺序必须是master先启动。
- 如果slave先启动,它会先等待30秒等待master 的启动,Master节点加入到集群中后,slave才启动。
- 如果30秒内master没有启动,slave会自动停止。
- 当所有节点因故(断电等) 同时离线时,每个节点都认为自己不是最后一个停止的节点,要恢复镜像队列,可以尝试在30秒内启动所有节点。
3.1 定义参数
参数含义:
ha-mode
:指明镜像队列的模式,可选项有 all、exactly、nodes,默认为 all。all
:表示在集群中所有的节点上进行镜像。exactly
:表示在指定个数的节点上进行镜像,节点个数由 ha-params 指定。nodes
: 表示在指定节点上进行镜像,节点名称由 ha-params 指定,节点的名称通常类似于 rabbit@hostname ,可以通过rabbitmqctl_cluster_status命令查看。
ha-params
:不同的 ha-mode 配置中需要用到的参数。ha-sync-mode
:队列中消息的同步方式,可选项有 automatic(自动) 和manual(手动)。ha-promote-on-shutdown
:可选参数when-synced(何时同步)和always(始终同步)。ha-promote-on-failure
:可选参数when-synced(何时同步)和always(始终同步)。
注意事项:
- ha-mode参数对排他(exclusive) 队列不生效,因为排他队列是连接独占的,当连接断开时队列会自动删除,所以实际上这个参数对排他队列没有任何意义。
- 将新节点加入已存在的镜像队列时,默认情况下ha-sync-mode取值为manual(手动),镜像队列中的消息不会主动同步到新的slave中,除非显式调用同步命令。当调用同步命令后,队列开始阻塞,无法对其进行其他操作,直到同步完成。
- 当 ha-sync-mode 设置为 automatic(自动)时,新加入的slave会默认同步已知的镜像队列。由于同步过程的限制,所以不建议对生产环境中正在使用的队列进行操作。
- 当所有slave都出现未同步状态,且ha-promote-on-shutdown参数设为always,那么不论 master 因为何种原因停止,slave 都会接管 master,优先保证可用性,存在消息丢失情况。当ha-promote-on-shutdown参数设置为 when-synced(默认)时,分两种情况:
- 若master因主动原因停掉,比如通过
rabbitmgctl stop
命令停止,那么slave不会接管 master,此时镜像队列不可用。 - 若master因被动原因停掉,比如 Erlang 虚拟机或者操作系统崩溃,那么 slave 会接管 master。
- 若master因主动原因停掉,比如通过
3.2 命令配置
命令格式:
rabbitmqctl set_policy [-p vhost] [–prioritypriority] [–apply-to apply-to] {name} {pattern} {definition}
参数释义:
[-p vhost]
参数:可选参数,针对指定vhost下的queue进行设置。[–prioritypriority]
:可选参数,policy的优先级。[–apply-to apply-to ]
参数:可选参数,指定对象为交换器还是队列。{name}
参数:自定义策略名称。{pattern}
参数: queue的匹配模式(正则表达式){definition}
参数:ha-mode 、ha-params、ha-sync-mode三部分。
1. 查看要进行镜像的队列。
2. 设置策略。
# 优先级为0。
#指定对象为所有交换器。
#策略名称为qingjun_policy。
#匹配正则为"以qingjun开头的所有交换器"。
#镜像策略为,在3个节点上进行镜像,并设置消息同步方式为自动同步。
[root@node1 ~]# rabbitmqctl set_policy --priority 0 --apply-to exchanges qingjun_policy "^qingjun*" '{"ha-mode":"exactly","ha-params":3,"ha-sync-mode":"automatic"}'
查看结果。
- 查看状态
3.4 相关命令
3.4.1 查看节点消息同步状态
[root@node1 ~]# rabbitmqctl list_queues name slave_pids synchronised_slave_pids
3.4.2 取消某队列同步消息功能
取消qingjue_queue队列的消息同步功能,注意这个队列是能匹配到上面设置的镜像队列策略的。
[root@node1 ~]# rabbitmqctl cancel_sync_queue qingjue_queue
3.4.3 手动同步某队列消息
手动给qingjue_queue队列进行消息同步,注意这个队列是能匹配到上面设置的镜像队列策略的。
[root@node1 ~]# rabbitmqctl sync_queue qingjue_queue
引用资料
- https://blog.csdn.net/yi_qingjun/article/details/128496818