之前提到过在集群环境中,队列只有元数据会在集群的所有节点同步,但是队列中的数据只会存在于一个节点;这不免让人失望:数据没有冗余容易丢数据甚至在durable的情况下,如果所在的节点当掉就要等待节点恢复.那么是不是有消息冗余的解决方案呢?是的,RabbitMQ自2.6.0开始就开始支持镜像队列(Mirrored Queue).消息会在节点之间复制,和其它的主从设计一样,它也有master和slave的概念;一旦某个节点当掉,会在其余的节点中选举一个slave作为master.要注意Mirrored Queue 也不是银弹,后面会提到它的局限.

 

现在就开始动手  

  

Uri uri = new Uri( "amqp://192.168.10.160:9992/" );
               ......   ......
ch.ExchangeDeclare(exchange, exchangeType, true);//,true,true,false,false, true,null);
ch.QueueDeclare(pic_process_queue, true, false , false, new Dictionary <string, string>() { { "x-ha-policy" , "all" } });
 ch.QueueBind(pic_process_queue, exchange, routingKey);
 ch.QueueDeclare(pic_process_queue2, true, false , false, null);
 ch.QueueBind(pic_process_queue2, exchange, routingKey);

 

  我们使用rabbitmqctl工具检查集群状态,注意我新建了一个脚本rabbitmq-util内容基本和rabbitmqctl一样,只是显示指定了cookie.

[root@localhost scripts]#  ./rabbitmq-util -n z_91@zen.com cluster_status

Cluster status of node 'z_91@zen.com' ...

[{nodes,[{disc,['z_92@zen.com']},{ram,['z_93@zen.com','z_91@zen.com']}]},

{running_nodes,['z_92@zen.com','z_93@zen.com','z_91@zen.com']}]

...done.

[root@localhost scripts]#  ./rabbitmq-util -n z_93@zen.com list_queues name pid slave_pids

Listing queues ...
qp_pic_queue    <'z_92@zen.com'.2.7008.0>       [<'z_93@zen.com'.2.6931.0>, <'z_91@zen.com'.2.7445.0>]
qp_pic_queue2   <'z_92@zen.com'.2.7013.0>       []
...done.
[root@localhost scripts]# 

 

  可以看到上面连接的Rabbit是92节点,端口9992;我们现在正常关闭92节点(使用rabbitmqctl而不是kill),现在我们修改Client的代码,连接到93节点 Uri uri = new Uri( "amqp://192.168.10.160:9993/" );运行同样的代码,和我们的预期一致:qp_pic_queue 由于声明了Mirrored 所有在92节点关闭之后,我们可以在93上重建队列;而qp_pic_queue2 就没有这么幸运了,抛出了404错误.看下面的截图:

 

如果只是在部分节点复制呢?    

 

   上面的例子是把消息在整个集群内进行复制,如果是指定在几个节点之间做消息镜像怎么办?其实编码实现上没有难度,因为x-ha-policy参数支持显示指定节点;下面是我截取的rabbit_amqqueue.erl中关于创建镜像队列的参数处理逻辑;下面是一段C#客户端代码显示指定要在哪些节点复制消息.

..\rabbitmq-server-2.8.7\src\rabbit_amqqueue.erl

determine_queue_nodes(Args) ->
    Policy = rabbit_misc:table_lookup(Args, <<"x-ha-policy">>),
    PolicyParams = rabbit_misc:table_lookup(Args, <<"x-ha-policy-params">>),
    case {Policy, PolicyParams} of
        {{_Type, <<"nodes">>}, {array, Nodes}} ->
            case [list_to_atom(binary_to_list(Node)) ||
                     {longstr, Node} <- Nodes] of
                [Node]         -> {Node,   undefined};
                [First | Rest] -> {First,  [First | Rest]}
            end;
        {{_Type, <<"all">>}, _} ->
            {node(), all};
        _ ->
            {node(), undefined}
    end.

 

声明一个yaqp_pic_queue

ch.ExchangeDeclare(exchange, exchangeType, true);//,true,true,false,false, true,null);

 ch.QueueDeclare(pic_process_queue, true, false , false,

 new Dictionary <string, object>() { { "x-ha-policy" , "nodes" }, { "x-ha-policy-params", new List<string >() { "z_91@zen.com", "z_93@zen.com" } } });

 ch.QueueBind(pic_process_queue, exchange, routingKey);

   

  用rabbitmqctl看看slave_pids

[root@localhost scripts]#  ./rabbitmq-util -n z_93@zen.com cluster_status
Cluster status of node 'z_93@zen.com' ...
[{nodes,[{disc,['z_92@zen.com']},{ram,['z_93@zen.com','z_91@zen.com']}]},
{running_nodes,['z_92@zen.com','z_91@zen.com','z_93@zen.com']}]
...done.
[root@localhost scripts]#  ./rabbitmq-util -n z_91@zen.com list_queues name pid slave_pids
Listing queues ...
qp_pic_queue    <'z_93@zen.com'.2.6931.0>       [<'z_91@zen.com'.2.7445.0>, <'z_92@zen.com'.3.235.0>]
yaqp_pic_queue  <'z_91@zen.com'.2.7875.0>       [<'z_93@zen.com'.2.7387.0>]
qp_pic_queue2   <'z_92@zen.com'.3.232.0>        []
...done.
[root@localhost scripts]# 

   

    只是实际操作的时候有点两难了:我们要在代码编写的时候就要指定这个参数,而运行时这些节点可是不一定活着的.如果显示指定的节点不是都处于在线状态,declare就会失败.节点调整的时候硬编码也会让我们陷入窘境,所以rabbitmq in action推荐的做法是使用x-ha-policy的all选项,即在全集群范围内复制消息.

  

半路增加新节点会如何?

 

    理想状态总是简单,真实环境总是复杂;如果我们要在运行时添加一个新的节点到集群中(添加节点这种事情这再正常不过),消息复制会怎么处理?如果有新节点加入,RabbitMQ不会同步之前的历史数据,只会复制新消息.这里的假设是随着消息的被consumer取走,最终所有的节点的数据都会对齐一致.

    一个自然的追问就是:master节点退出集群会选一个slave作为master,那么要是不幸选中了一个刚刚加入集群的节点怎么办?不就丢消息了么?放心RabbitMQ会维护节点的状态是否已经同步,使用rabbitmqctl的synchronised_slave_pids参数,就可以查看状态.看下面的例子,如果slave_pids和synchronised_slave_pids里面的节点是一致的,那说明全都同步了.如果不一致很容易比较出来哪些还没有同步.

[root@localhost scripts]#  ./rabbitmq-util -n z_91@zen.com list_queues name pid slave_pids synchronised_slave_pids
Listing queues ...
zen_qp_pic_queue        <'z_91@zen.com'.2.8009.0>       [<'z_93@zen.com'.2.7517.0>]     [<'z_93@zen.com'.2.7517.0>]
qp_pic_queue    <'z_93@zen.com'.2.6931.0>       [<'z_91@zen.com'.2.7445.0>, <'z_92@zen.com'.3.235.0>, <'z_94@zen.com'.1.595.0>] [<'z_91@zen.com'.2.7445.0>, <'z_92@zen.com'.3.235.0>, <'z_94@zen.com'.1.595.0>]
yaqp_pic_queue  <'z_91@zen.com'.2.7875.0>       [<'z_93@zen.com'.2.7387.0>]     [<'z_93@zen.com'.2.7387.0>]
qp_pic_queue2   <'z_92@zen.com'.3.232.0>
yy_qp_pic_queue <'z_91@zen.com'.2.7920.0>       [<'z_93@zen.com'.2.7434.0>]     [<'z_93@zen.com'.2.7434.0>]
...done.
[root@localhost scripts]# 

 

 官网资料: Highly Available Queues  http://www.rabbitmq.com/ha.html 

 

最后,小图一张 每当变幻时 Miss