Rabbitmq集群和镜像队列
1引言
1.1编写目的
2 原理和使用
2.1镜像队列原理
2.1.1 原理
默认的一个rabbitmq中的queue是在一个node上的,至于在那个node上取决于client 进行declared的时候的顺序,于此不同的是rabbitmq中的exchange、bindings都是需要在全部的节点上存在的,也就是rabbitmq集群天生就是支持自动同步这些信息的。而queue则是可以通过mirrored 同步到多个节点上,至于到底是几个节点可以指定。
其实rabbitmq集群中的queue不是没有同步,是有同步的,只是他们同步的只是queue的定义部分或者说声明部分,并没有同步queue里面的msg,要想实现也同步queue里面的msg 就需要使用镜像队列了
每一个queue都会创建在一个master node或者 多个slaves node上 ,并且当master 丢失的时候,最老的slaves node将会变成最新的master,当然这有一个前提那就是要求slave node必须已经同步了master node的内容,如果没有同步的话那么这个slave node是不可以成为master node的
2.1.2 rabbitmq的集群和镜像队列区别
rabbitmq的集群是根据erlang的同步机制来实现的,这是erlang自带的功能,十分强大,如果几个rabbitmq node进行了集群后,这些node 之前是可以同步 元数据的,包括exchange的元数据、队列的元数据、binding的元数据,当时需要注意的是不会包括同步queue的内容默认情况下。主要是因为如果同步的queue的内容的话,如果出现了queue很多的时候,并且每一个queue的内容都很大的时候,rabbitmq就仅仅需要忙着处理这些本node的queue还要负责处理和不同的node之前进行同步,这会造成系统很大负载,对rabbitmq的整体性能会有很多的影响,因此默认是关闭的。那么如果你非要进行队列的内容同步,比如你的队列吞吐量并不高,每一个队列的内容也不大,对性能不会造成什么影响,那么就可以打开镜像队列,这个时候集群之前就会同步queue的内容了。
镜像队列其实就是原来channel只会按照binding来把消息路由给某个队列,现在如果你使用了镜像队列,那么channel就需要并行的把消息路由给master node 同时还需要把消息同步给slave node上,所以当使用镜像队列以后性能是一定会下降的
另外说下rabbitmq的集群node中必须有一个要配置为磁盘类型,这个是防止数据丢失的,另外如果整个集群都停掉了那么应该要保证最后一个down掉的node首先被启动,如果不可以则要使用forget_cluster_node命令将最后关闭的那个node移出集群,如果集群中的节点几乎同时以不可控的方式down了,此时在其中一个节点使用force_boot命令重启节点
另个rabbitmq集群重用的命令:reset 可以把一个node从一个集群中移出,forget_cluster_node则是可以在另一个节点上把某一个node移出自己的集群
2.1.4 queue的master和slave node
每一个queue都有一个home node也叫主队列。所有的镜像操作首先必须要通过这个master,然后在复制到其他的mirrors上,这样做主要是为了保证消息的FIFO顺序,也就是说所有顺序执行的操作都要顺序交给master,然后channel在同步给slave node,如果master上移出了队列中的内容,那么所有的slave node也同步的移出所有的队列内容。但是要记住consumer消费的只是 master node,并不是slave node
可以通过如下的策略来决定 queue master 在那个节点上:
- 选择存在主队列 最少的的节点: min-masters
- 选择client 声明queue 连接的节点:client-local
- 随机选择:random
以上策略的配置可以通过如下几个方法:
- 在创建queue的时候使用x-queue-master-locator来指明用上面的那个策略
- 在配置文件中使用配置项 queue_master_locator指明使用上面的某个策略
2.1.3 镜像队列的配置使用
早期的rabbitmq版本要想配置队列使用使用mirror queue,必须要consumer创建队列的时候,使用参数来指明。这种方法的不好之处在于不能从rabbitmq server端统一进行配置管理。后来进行了改进,rabbitmq通过policy来激活镜像队列,policy可以在任何时候进行修改,并且修改后立即生效,比如你可以在任何时候把一个非镜像队列通过policy修改成一个镜像队列,反这亦然。另外当一个镜像队列只有一个master 没有slaves的时候,他和非镜像队列也是不一样的,虽然看起来是一样的,相比较而言非镜像队列的性能要好很多
下面说下镜像队列的通过policy方法进行配置的方法
为了让一个queue或者对个queue变成mirror queue 必须要产生一个policy,这个policy会使用表达式语法和队列的名字进行匹配,并且会使用policy的key:ha-mode 和可选的ha-params 决定怎么同步queue
ha-mode | ha-params | 结果 |
all | 所有node都要复制queue,并且当集群中添加一个新的node的时候,queue也会复制过去 | |
extactly | count | queue只会同步到ha-params中指定的count个node上,如果集群中的node个数小于count,queue将会同步到整个集群中,如果集群中的node个数大于count,当一个node包含一个mirror queue并且down的时候,一个新的mirror将会被创建 |
nodes | node names | 消息被mirror到指定的node上通过指定node name,node name是erlang node name 可以通过 rabbitmqctl cluster_status 查看,一般的形式是rabbit@hostname |
不管什么时候发生了镜像队列的 policy 变化,rabbitmq都会尽力保持现有的mirror 但是同时要和新的policy要尽量符合
下面是几个例子:
- 所有以ha.开头的队列并镜像到集群中的所有其他队列中
rabbitmqctl set_policy ha-all "^ha." '{"ha-mode":"all"}'
- 所有以two.开头的队列并镜像到集群中任意两个node上
rabbitmqctl set_policy ha-two "^two." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
- 所有以test.开头的队列并镜像到集群中指明名字的两个node上
rabbitmqctl set_policy ha-nodes "^nodes."
'{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}
2.1.5 nodes policy 对queue master的影响
如果在改变镜像队列的 policy的时候 选择了 nodes 类型的policy 这个有可能会造成
原来存在的queue master 消失,如果这个queue master 所在的node 不在 nodes list内。为了阻止queue master的丢失,rabbitmq将会保持queue master 存在直到slaves开始同步。不过一旦同步开始,那么rabbitmq 就不会继续保证这个 queue master 还需要保持了,这个时候consumer就会失去和queue master的连接,然后需要重新连接。所以这就是说一旦queue master down,那么consumer就一定会出现一次reconnection,只是在reconnection之前你有没有包装数据的同步
例如 如果一个queue is on [A,B] 其中 node A上是 queue master ,此时如果你创建了一个policy 类型的 nodes 告诉 rabbitmq 消息需要在node [C,D] 那么在初始阶段 queue是在[A,C,D]三个node上,但是一旦同步那么就queue就会在[C,D]上,master A queue 将会被关闭.所以是要手动同步还是要自动同步,也需要我们仔细考虑
2.1.6 排他性队列(exclusive queues)
排他性队列是一种和connection生命周期关联的队列,一旦connection关闭那么排他性队列就会被删除,可以想想下如果一个node crash了 那么和这个node 建立的connection都会被关闭所以排他性队列也会被删除,因此这样的队列不应该被mirror
2.2镜像队列使用
2.2.1 新节点同步机制
如果集群中新加了一个node,那么这个node就会变成备复制节点,而且备复制节点不会主动的复制master node 上的queue的内容,当然了他是会同步master node上的queue的元数据的,那么怎么实现和master node上的queue内容同步呢,这就需要随着时间的推移一点一点的来同步了,也就是说新加的节点只会同步后来加入到master node上的队列内容并且随着master node上的queue里面的内容被不断的消费掉,很快备份节点和master node上面的内容就会一样了。
但是考虑到万一consumer正好发布消息到了新的node上,并且master node 又挂了会怎么样呢,答案是会丢失消息。因此在master node 将现存的queue的内容复制到其他的新节点之前,分辨是否所有从copy和master node拥有相同的内容就很重要了。为了检测镜像队列同步的状态,可以使用如下的命令来确定:
rabbitmqctl list_queues pid slave_pids synchronised_slave_pids
这个命令会显示出 slave-pids 和synchronised_slave_pids
如果两个是一样的时候就表示所有的队列内容同步完成了否则就是还没有同步完成
这个时候master node是一定不可以删除的
2.2.2 停止所有node后slave删除队列内容
当所有node停止后再重启,如果之前有声明为持久化的队列,那么master node上会继续保留有之前queue的全部信息和内容,但是slave node只会同步到queue的元数据,并不会同步到queue的内容,主要是slave node 重启后并不知道自己原来保留的queue的内容是否和master node上的queue的内容一致,所以干脆就删除全部的消息内容,感觉就相当于一个新的node刚刚加入一个集群一样
2.2.3 停止只有未同步的slave的master node
集群中可能会出现所有的slave node都未同步完成的时候,master被关闭了,虽然这种情况出现的可能性不会太大,但是是有这个可能的。这个关闭可能是下面两个情况:
有意的或者说可控的关闭了master node,比如有意的停止rabbitmq或者关闭了服务器,这个时候为了避免消息丢失,rabbitmq不会同步fail over 到其他的还未同步完成的slave上,相反的整个queue将会被stop掉,给人的感觉就好像是slave node根本不存在一样
无意的不是人为的,而是由于rabbitmq crash了或者突然掉电了,这个是会出发master node 自动 fail over 到未同步完成的slave node上
如果实在是想即使slave node没同步,mater node关闭后也要fail over到slave node上那么可以使用rabbitmq的policy:设置policy中的ha-promote-on-shutdown的默认值when-synced改为always
2.2.4 所有slave 关闭时候 master 异常关闭
这种情况更少见,主要是slave 都处于关闭状态时候,master node 不知道什么原因不在集群中了,一般情况下,slave都关闭了,master丢失的时候,根据上一个场景"停止只有未同步的slave的master node" 我们可以知道这个时候slave如果没有同步就被关闭了那么master消失后,queue也没有了master。但是如果我们执行 forget_cluster_node 移出master的时候,rabbitmq会试图为master node上每一个队列找到一个当前被stop的slave node,并在这个slave node重启后被提升为master,如果有多个slave node可以选择,那么最近关闭的slave node会被选中
这个需要理解为什么我们只能选择stop的slave node,而不是运行的slave node呢?答案其实在场景"停止所有node后消息同步"中说的一样,一旦关闭了slave node重启后就会自动删除所有的本地queue的内容了