【概述】
rabbitmq采用了镜像队列的方式实现队列的高可用,镜像队列的使用、配置、内部实现原理网上有很多文章都有介绍,自己很久之前也曾总结过相关内容。但回过头再来看镜像队列,仍然有新的发现,本文分析总结了镜像队列生产消费的数据流以及节点流量,以便更好的使用镜像队列。
【生产消费的数据流】
分析镜像队列之前,我们还是先一步步从单机情况下,集群情况下看看生产消费的数据流走向是怎样的,最后再分析镜像队列的场景。
-
单机场景:
如上图所示,生产者,消费者连接到rabbitmq后,在rabbitmq内部会创建对应的connection,channel进程。connection进程从socket上接收生产者发送消息后投递到channel进程;在channel进程中,根据消息发送的exchange与消息的routing-key,在内部数据库的路由表中,查找所有匹配的队列的进程PID,然后将消息投递到这些队列的进程中。
同样,队列进程收到消息后,找到消费者对应的channel进程PID,然后将消息投递给该进程,channel进程再发送给对应的connection进程,然后通过socket发送给消费者。
从上面可以看出,一条消息从生产到消费,rabbitmq的节点上会有1进1出的流量。
补充说明下:在rabbitmq内部会为每个tcp链接创建多个进程,其中包括专门用于数据接收的rabbit_reader进程,专门用于数据发送的rabbit_writer进程,本文为了方便表达,统一使用connection进程进行socket数据的收发。所以上面提到的connection进程可以理解为包括了rabbit_reader,rabbit_writer进程。
-
集群场景
我们都知道,每个队列都有一个唯一的master进程,所有生产消费的消息都是由master进程负责处理的。
在集群场景中,生产者消费者连接到集群中的任意节点都可以正确将消息投递到队列和正确从队列消费消息。如果生产者消费者连接的节点恰好是队列master所在的节点,那么这种情况下和单机场景是一样的。但更多的时候会存在这样的情况:生产者、消费者连接的rabbitmq节点并不是队列master进程所在的节点,那么生产消费的消息数据就会在集群的这两个节点上传输。
如上图所示,生产消费的流程其实和单机场景一模一样,在channel进程中查数据库找到匹配的队列的进程PID,然后将消息投递到这个队列进程中。
但是,这里的队列进程是在另外一个节点上,rabbitmq会自动识别这一点,然后通过与对端节点的分布式端口(默认为25672)通信,将消息发送到对端节点的进程中。也就是说,消息在集群中进行了传输。
同样,队列收到消息后,找到消费者对应的channel进程PID,并将消息发送到这个进程中,这里也会进行一次集群通信,用于消息的传输。
在这种场景下,一条消息从生产到消费,上图中node1节点是2进2出的流量,node2节点是1进1出的流量。
因此,尽可能的让生产者、消费者与队列master进程位于同一个节点上,可以减少集群间的网络交互。
-
镜像队列
先来看下生产者、消费者连接的节点就是队列master进程所在节点的情况,如下图所示:
流程还是和之前一样,但是不同的地方有两点:
1) 在channel进程中,查数据库找匹配的队列的进程。对于镜像队列而言,除了所有匹配的队列的master进程外,还包括所有的slave进程。也就是说,channel进程除了将消息发送给队列的master进程外,还会将消息发送给队列所有的slave进程,而slave进程肯定都是在远端节点上,因此这里就多了一次集群间的网络交互。
2)镜像队列的master进程收到后,需要负责将消息同步给所有的slave进程,rabbitmq采用的GM算法实现中,镜像队列中的master和所有slave都会发送一次消息和接收一次消息,同时还会发送一次对消息的ack,和接收一次消息的ack。(镜像队列的同步实现原理,这里不展开详细说明,可参考文章https://my.oschina.net/hncscwc/blog/186350)
综上所述,生产者发送一条消息,队列master进程所在节点会收到两次:一次是生产者发送的,一次是队列slave进程发送的;同样也会将消息对外发送两次:一次是生产者对应的channel进程将消息发送给队列的slave进程;一次是队列的master进程进行广播同步将消息发送给slave进程。此外,镜像队列的GM算法实现 ,每条消息还会有额外的确认消息在集群间进行发送。
再结合图中的情况,一条消息从生产者到消费,Node1节点是2进3出的流量,Node2节点是2进1出的流量。
和集群中的场景一样,如果生产者消费者连接的节点不是镜像队列master进程所在的队列,一条消息从生产到消费,生产者消费者连接的节点是3进3出,队列master进程所在的节点是2进2出。
【总结】
从前面的分析不难看出,镜像队列在集群中所增加的网络通信是较大的,尤其是出现跨节点通信的情况,因此,队列数量不多,并且队列的消息量不大的情况下,可以考虑使用;而在队列数量较多,并且队列都有较大的消息量的情况下使用镜像队列,需要在高可用,高性能之间进行权衡,因为集群间的网络流量可能会是非常大的,实际测试时通过netstat发现rabbitmq的分布式端口,其发送和接收队列都有大量的数据堆积,导致性能出现瓶颈。
另外,镜像队列可以调整的参数不多,官方也在3.8.0版本中推出了新的队列模式(quorum queue)用来替代镜像队列的方案。有兴趣的朋友可以尝鲜研究下。