RabbitMQ 集群

                            RabbitMQ 集群
你可以使用若干个RabbitMQ 节点组成一个 RabbitMQ 集群。集群解决的是扩展性问题。所有的数据和状态都会在集群内所有的节点上被复制,只有queue是例外。默认的情况下,消息只会存在于它被创建的节点上,但是它们在所有节点上可见和可访问。
    对于Queue 来说,消息实体只存在于其中一个节点,A、B两个节点仅有相同的元数据,即队列结构。当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer。所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连A或B,出口总在A,会产生瓶颈。该模式存在一个问题就是当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了消息持久化,那么得等A节点恢复,然后才可被消费;如果没有持久化的话,然后就没有然后了。
   因此,在集群环境中,队列只有元数据会在集群的所有节点同步,但队列中的数据只会存在于一个节点,数据没有冗余且容易丢,甚至在durable的情况下,如果所在的服务器节点宕机,就要等待节点恢复才能继续提供消息服务。那既然有这种问题,为什么依然有这个选项呢?官方的说法是:
   (1)存储空间:如果集群的每个节点都有每个queue的一个拷贝,那么增加节点将无法增加存储容量。比如,如果一个节点可以存放 1GB 的消息,增加另外两个节点只会增加另外两个拷贝而已。
  (2)性能:发布消息,将会将它拷贝其它节点上。如果是持久性消息,那么每个节点上都会触发磁盘操作。你的网络和磁盘负载在每次增加节点时都会增加。
  可见,RabbitMQ Clustering 技术不能完全解决HA 问题。单纯的集群只适合于在不需要HA的场景中使用。
一.基于集群+镜像队列的方案
1.配置
    多个单独的 RabbitMQ 服务,可以加入到一个集群中,也可以从集群中退出。集群中的 RabbitMQ 服务,使用同样的 Erlang cookie(unix 系统上默认为 /var/lib/rabbitmq/.erlang.cookie)。所有在一个 RabbitMQ 服务中被操作的数据和状态(date/state)都会被同步到集群中的其它节点。 
   镜像队列的配置通过添加 policy 完成,policy 添加的命令为:
   rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
   -p Vhost: 可选参数,针对指定vhost下的queue进行设置
   Name: policy的名称
   Pattern: queue的匹配模式(正则表达式)
   Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
   ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
   all表示在集群所有的节点上进行镜像
   exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
   nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
   ha-params: ha-mode模式需要用到的参数
   ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
   Priority: 可选参数, policy的优先级
   例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
2.2 HAProxy 和 RabbitMQ 集群

创建 queue 的过程:

LB 将 client request 分发到 node 2,client 创建队列 “NewQueue”,然后开始向其中放入 message。
最终,后端服务会对 node 2 上的 “NewQueue” 创建一个快照,并在一段时间内将其拷贝到node 1 和 3 上。这时候,node2 上的队列是 master Queue,node 1 和 3 上的队列是 slave queue。
假如现在 node2 宕机了:

node 2 不再响应心跳,它会被认为已经被从集群中移出了
node 2 上的 master queue 不再可用
RabbitMQ 将 node 1 或者 3 上的 salve instance 升级为 master instance
假设 master queue 还在 node 2 上,客户端通过 LB 访问该队列:

客户端连接到集群,要访问 “NewQueue” 队列
LB 根据配置的轮询算法将请求分发到一个节点上
假设客户端请求被转到 node 3 上
RabbitMQ 发现 “NewQueue” master node 是 node 2
RabbitMQ 将消息转到 node 2 上
最终客户端成功连接到 node 2 上的 master 队列
可见,这种配置下,2/3 的客户端请求需要重定向,这会造成大概率的访问延迟,但是终究访问还是会成功的。要优化的话,总共有两种方式:

直接连到 master queue 所在的节点,这样就不需要重定向了。但是对这种方式,需要提前计算,然后告诉客户端哪个节点上有 master queue。
尽可能地在所有节点间平均分布队列,减少重定向概率
2.3 镜像队列的负载均衡

     使用镜像队列的 RabbitMQ 不支持负载均衡,这是由其镜像队列的实现机制决定的。如前面所述,假设一个集群里有两个实例,记作 rabbitA 和 rabbitB。如果某个队列在rabbitA 上创建,随后在 rabbitB 上镜像备份,那么 rabbitA 上的队列称为该队列的主队列(master queue),其它备份均为从队列。接下来,无论client 访问rabbitA 或 rabbitB,最终消费的队列都是主队列。换句话说,即使在连接时主动连接rabbitB,RabbitMQ的 cluster 会自动把连接转向 rabbitA。当且仅当rabbitA服务down掉以后,在剩余的从队列中再选举一个作为继任的主队列。
    如果这种机制是真的(需要看代码最最终确认),那么负载均衡就不能简单地随机化连接就能做到了。要实现轮询,需要满足下面的条件:
   队列本身的建立需要随机化,即将队列分布于各个服务器
   client 访问需要知道每个队列的主队列保存在哪个服务器
   如果某个服务器down了,需要知道哪个从队列被选择成为继任的主队列。

   要实现这种方案,首先,在建立一个新队列的时候,Randomiser 会随机选择一个服务器,这样能够保证队列均匀分散在各个服务器(这里暂且不考虑负载)。建立队列后需要在Meta data 里记录这个队列对应的服务器;另外,Monitor Service是关键,它用于处理某个服务器down掉的情况。一旦发生down机,它需要为之前主队列在该服务器的队列重新建立起与服务器的映射关系。

这里会遇到一个问题,即怎么判断某个队列的主队列呢?一个方法是通过rabbitmqctl,如下面的例子:
./rabbitmqctl -p production list_queues pid slave_pids
registration-email-queue        <[email protected]>       [<[email protected]>]
registration-sms-queue  <[email protected]>       [<[email protected]>]
可以看到pid和slave_pids分别对应主队列所在的服务器和从服务器(可能有多个)。
  利用这个命令就可以了解每个队列所在的主服务器了。
参考:
http://www.cnblogs.com/sammyliu/p/4730517.html

猜你喜欢

转载自chenja.iteye.com/blog/2378628