RabbitMQ介绍5 - 集群

RabbitMQ内建集群机制,利用Erlang提供的开放电信平台(OTP,Open telecom Platform)通信框架,使得集群很容易进行横向扩展,提高系统吞吐量。这里只讨论集群的概念、原理,关于如何创建集群见官方介绍: http://www.rabbitmq.com/clustering.html

内存节点和磁盘节点

RabbitMQ将队列、exchange、绑定、vhost的配置信息(也就是创建时提供的信息)称为元数据(集群状态时,集群节点地址、节点与其它元数据的关系也是元数据),简单理解--除掉MQ中消息内容本身,其它的都是元数据。在作为单独节点运行时,RabbitMQ将所有的元数据保存在内存中,将标记为可持久化的队列,exchange(和相应的绑定)保存到磁盘。作为集群时,这些元数据可能仅仅在内存中(称为内存节点RAM node),或同时保存在硬盘中(称为磁盘节点disk node),每个集群最少有一个磁盘节点,可有0个或多个内存节点。单一节点的RabbitMQ必须是一个磁盘节点。问题:磁盘节点会保存非持久化队列的信息到磁盘吗?内存节点会保存持久化的消息到磁盘吗?都会。

clip_image001

Metadata writes in RAM and disk nodes.

磁盘节点保证集群重启的时候可以重建持久化的Exchange,Queue等信息,所有这些信息的更新都必须先通过磁盘节点保存。所以磁盘节点太多影响性能,磁盘节点太少不安全。这里的性能指的是创建Exchange,queue的性能,一般我们不需要经常创建、更新这些信息,使用磁盘节点不会有性能影响,但对于RPC之类的应用场景,需要频繁的创建和删除queue,便需要考虑性能问题了。

如果集群中的磁盘节点都崩溃的话,集群可以继续进行路由工作,但是不能进行元数据改动的操作,例如创建Exchange、queue,添加删除节点。

添加删除节点。节点加入或离开集群至少要通知一个磁盘节点,正常的情况是需要全部的磁盘节点在线,并更新全部磁盘节点(如果有多个磁盘节点,只通知了其中的一个,集群会出问题)。内存节点重启时会连接一个硬盘节点,获得集群元数据拷贝(内存节点唯一存储到磁盘的元数据是磁盘节点的地址)。

队列和Exchange在集群中的工作方式

队列只会在集群的一个节点上创建,也就是说队列的消息只会出现在一个节点(发送者和消费者都有可能连接到集群的其它节点,但它们都必须通过创建队列的节点首发消息),当然集群中的所有节点都知道到哪能找到这个队列。当队列所在的这个节点崩溃的时候,这个队列便从集群中消失了,附加在队列上的消费者会丢失订阅(收到异常),发送者的消息自然也无法发送到这个队列,也就是消息丢失了。这时候需要重建队列(捕获异常,重建),但是如果队列是持久化的,无法重建队列,只能等待故障节点恢复。队列只在一个节点创建的策略是出于性能的考虑,这样数据没有冗余,可以提高节点吞吐量,方便横向扩展。

clip_image002

Queue behavior in standalone and cluster configurations

Exchange会在集群的全部节点上创建,因为Exchange本质上是一个名称和绑定的列表(可以理解为一张查询表),在集群每个节点都保留一份并不会有性能开销。每次创建Exchange的时候,这个信息会发布到所有节点上。这样,在有节点崩溃的时候,连接在节点上的终端只需要连接到集群的其它节点,不需要重建Exchange,但是如果使用镜像队列的主备模式,不能保证exchange会恢复,所以,好习惯是终端每次连接RabbitMQ时都创建exchange。

clip_image003

Exchange and queue distribution in a cluster.

镜像队列

为了实现高可用性(记得持久化队列所在节点崩溃的情况么),RabbitMQ从版本2.6.0开始,提供镜像队列功能。设置为镜像的队列会在集群的多个节点上创建,这样消息会有备份。一个主队列Master可以有多个从队列slave,一旦master不可用,最老的slave成为新的master。官方介绍:http://www.rabbitmq.com/ha.html

创建镜像队列

可以在客户端通过声明队列的时候设置参数x-ha-policy来创建镜像,例如下面将队列hello-queue镜像到所有节点。

queue_args = {"x-ha-policy" : "all"}

channel.queue_declare(queue="hello-queue", arguments=queue_args)

如果要指定作为slave的节点,可以像下面这样提供slave列表。

queue_args = {"x-ha-policy" : "nodes", "x-ha-policy-params" : [rabbit@testServer]}

channel.queue_declare(queue="hello-queue", arguments=queue_args)

 

也可以在服务器端通过设置Policy来配置,见连接 http://www.rabbitmq.com/ha.html#genesis 。一般采用服务端配置,实现简单,方便修改。

规则可以用来配置exchang和queue上的参数(关于Policy的解释: http://www.rabbitmq.com/parameters.html#policies ),类似于客户端创建exchange和queue提供的参数,但是并不完全一样。每个exchange或queue只能应用一个规则,如果有多个规则匹配,则使用优先级最高的规则。每个规则可以配置多个参数,例如federation-upstream-set和ha-mode,通过这两个参数可以控制federation(跨网同步插件)和mirror(镜像机制)。

问题:主节点是哪个?这取决于master选择的策略,通过参数x-queue-master-locator(规则queue_master_locator)可以设置策略。

  • Pick the node hosting the minimum number of masters: min-masters
  • Pick the node the client that declares the queue is connected to: client-local
  • Pick a random node: random

镜像队列和异常处理

信道将消息投递到master队列和所有的slave队列,类似于fanout模式的exchange将消息路由到所有绑定的队列。这样,在使用事务或发送方确认模式时,需要多个成功事务或发送方确认消息。

clip_image001

异常时消费者的处理方式。

  • 当slave队列所在的节点崩溃时,连接在这个节点上的消费者会丢失连接,重新连接到集群后可以重新附到master队列上;master队列所在节点上的消费者没有影响。
  • 当master队列所在的节点崩溃时,连接在主节点的消费者会丢失连接,从新连接到集群后可以附到新的master队列;连接在slave队列节点上的消费者会收到一个消费者取消通知,这时候需要重新连接集群,附到新的master队列上。但有的AMQP客户端不支持消费者取消通知的含义(支持的客户端会抛出异常),这时候消费者只会傻傻等待,以为队列是空的,而事实上队列已经慢慢被消息塞满了。
  • 主节点崩溃时尚未确认的消息会重新投递,因为新的主节点无法确认这些消息是确实消费者没有确认,还是由于主节点崩溃导致丢失了消费者的确认,为安全起见,会回到原位置(2.7.0版本之前会回到队列末尾)重新投递。

从故障中恢复

构建RabbitMQ集群来确保可用性和性能只是保障弹性消息通信基础架构的一半,另一半是编写当节点发生故障时知道如何重连到集群的应用程序。处理到集群的重连有多种策略,比如让程序知道所有的节点地址,出问题后可以尝试其他的节点;或者使用负载均衡,这样应用只需要知道负载均衡地址,而且可以比较均匀的将连接分布到各个节点。这里只讨论负载均衡工作方式。

负载均衡为一组服务器提供单一的访问地址,见网上介绍:https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1_(%E8%AE%A1%E7%AE%97%E6%9C%BA)

RabbitMQ集群使用负载均衡。客户端通过共同的地址(例如localhost:5670)连接到负载均衡设备(或者负载均衡软件),负载均衡负责将连接分配到集群的具体节点。

clip_image002[5]

Load balancing a RabbitMQ cluster.

连接丢失和故障转移

当集群节点出现问题时,应用程序必须要做出决定:下一个该连向哪里?应用程序需要重新连接到集群,并且重建连接、channel,和exchange、queue,也就是每次重连的时候想象自己连接了一个全新的MQ。C#客户端会自动处理重连(参考连接:http://www.rabbitmq.com/dotnet-api-guide.html#connection-recovery),重连分为两个部分:Connection和Topology。connection部分负责网络连接的重建,topology负责exchange、queue、绑定的重建。

Automatic Recovery From Network Failures

Connection Recovery

RabbitMQ .NET/C# client supports automatic recovery of connections and topology (queues, exchanges, bindings, and consumers). The automatic recovery process for many applications follows the following steps:

  1. Reconnect
  2. Restore connection listeners
  3. Re-open channels
  4. Restore channel listeners
  5. Restore channel basic.qos setting, publisher confirms and transaction settings

Topology recovery includes the following actions, performed for every channel

  1. Re-declare exchanges (except for predefined ones)
  2. Re-declare queues
  3. Recover all bindings
  4. Recover all consumers

To enable automatic connection recovery, set ConnectionFactory.AutomaticRecoveryEnabled to true:

ConnectionFactory factory = new ConnectionFactory();
factory.AutomaticRecoveryEnabled = true;
// connection that will recover automatically
IConnection conn = factory.CreateConnection();

If recovery fails due to an exception (e.g. RabbitMQ node is still not reachable), it will be retried after a fixed time interval (default is 5 seconds). The interval can be configured:

ConnectionFactory factory = new ConnectionFactory();
// attempt recovery every 10 seconds
factory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);

Topology Recovery

Topology recovery involves recovery of exchanges, queues, bindings and consumers. It is enabled by default but can be disabled:

ConnectionFactory factory = new ConnectionFactory();

Connection conn = factory.CreateConnection();
factory.AutomaticRecoveryEnabled = true;
factory.TopologyRecoveryEnabled  = false;

Manual Acknowledgements and Automatic Recovery

When manual acknowledgements are used, it is possible that network connection to RabbitMQ node fails between message delivery and acknowledgement. After connection recovery, RabbitMQ will reset delivery tags on all channels. This means that basic.ack, basic.nack, and basic.reject with old delivery tags will cause a channel exception. To avoid this, RabbitMQ .NET client keeps track of and updates delivery tags to make them monotonically growing between recoveries. IModel.BasicAck, IModel.BasicNack, andIModel.BasicReject then translate adjusted delivery tags into those used by RabbitMQ. Acknowledgements with stale delivery tags will not be sent. Applications that use manual acknowledgements and automatic recovery must be capable of handling redeliveries.

Tip:如果消费者通过EventingBasicConsumer接收消息,使用connection自动重建不会恢复消息监听。

心跳

集群各个节点之间,RabbitMQ服务和客户端之间都需要监测TCP连接,以便网络断开时执行相关处理。操作系统可能需要花点时间才会发现连接出了问题,比如Linux默认需要约11分钟后才会检测到。AMQP 0-9-1提供心跳机制以便更快的检测网络情况,同时,心跳也能防止有些网络设备关掉“idle”的TCP连接。RabbitMQ实现了这个机制( http://www.rabbitmq.com/heartbeats.html )。

心跳超时时间

服务器和客户端连接的时候会协定一个心跳超时时间,如果超过这个时间还没有收到心跳,则认为连接断开。3.5.5之前心跳的默认超时时间是580s,之后是60s。心跳包的发送间隔是timeout/2,也就是丢失2个心跳包便会认为断开。将超时时间设为0表示禁用心跳。

var cf = new ConnectionFactory();

// set the heartbeat timeout to 60 seconds

cf.RequestedHeartbeat = 60;

posted on 2016-02-17 11:01  ShaunLing  阅读(1256)  评论(0编辑  收藏  举报