RabbitMQ 集群

阿祖哥 2017-06-08

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)性能:发布消息,将会将它拷贝其它节点上。如果是持久性消息,那么每个节点上都会触发磁盘操作。你的网络和磁盘负载在每次增加节点时都会增加。

可见,RabbitMQClustering技术不能完全解决HA问题。单纯的集群只适合于在不需要HA的场景中使用。

一.基于集群+镜像队列的方案

1.配置

多个单独的RabbitMQ服务,可以加入到一个集群中,也可以从集群中退出。集群中的RabbitMQ服务,使用同样的Erlangcookie(unix系统上默认为/var/lib/rabbitmq/.erlang.cookie)。所有在一个RabbitMQ服务中被操作的数据和状态(date/state)都会被同步到集群中的其它节点。

镜像队列的配置通过添加policy完成,policy添加的命令为:

rabbitmqctlset_policy[-pVhost]NamePatternDefinition[Priority]

-pVhost:可选参数,针对指定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的设置命令为:

rabbitmqctlset_policyhello-ha"^hello"'{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

2.2HAProxy和RabbitMQ集群

创建queue的过程:

LB将clientrequest分发到node2,client创建队列“NewQueue”,然后开始向其中放入message。

最终,后端服务会对node2上的“NewQueue”创建一个快照,并在一段时间内将其拷贝到node1和3上。这时候,node2上的队列是masterQueue,node1和3上的队列是slavequeue。

假如现在node2宕机了:

node2不再响应心跳,它会被认为已经被从集群中移出了

node2上的masterqueue不再可用

RabbitMQ将node1或者3上的salveinstance升级为masterinstance

假设masterqueue还在node2上,客户端通过LB访问该队列:

客户端连接到集群,要访问“NewQueue”队列

LB根据配置的轮询算法将请求分发到一个节点上

假设客户端请求被转到node3上

RabbitMQ发现“NewQueue”masternode是node2

RabbitMQ将消息转到node2上

最终客户端成功连接到node2上的master队列

可见,这种配置下,2/3的客户端请求需要重定向,这会造成大概率的访问延迟,但是终究访问还是会成功的。要优化的话,总共有两种方式:

直接连到masterqueue所在的节点,这样就不需要重定向了。但是对这种方式,需要提前计算,然后告诉客户端哪个节点上有masterqueue。

尽可能地在所有节点间平均分布队列,减少重定向概率

2.3镜像队列的负载均衡

使用镜像队列的RabbitMQ不支持负载均衡,这是由其镜像队列的实现机制决定的。如前面所述,假设一个集群里有两个实例,记作rabbitA和rabbitB。如果某个队列在rabbitA上创建,随后在rabbitB上镜像备份,那么rabbitA上的队列称为该队列的主队列(masterqueue),其它备份均为从队列。接下来,无论client访问rabbitA或rabbitB,最终消费的队列都是主队列。换句话说,即使在连接时主动连接rabbitB,RabbitMQ的cluster会自动把连接转向rabbitA。当且仅当rabbitA服务down掉以后,在剩余的从队列中再选举一个作为继任的主队列。

如果这种机制是真的(需要看代码最最终确认),那么负载均衡就不能简单地随机化连接就能做到了。要实现轮询,需要满足下面的条件:

队列本身的建立需要随机化,即将队列分布于各个服务器

client访问需要知道每个队列的主队列保存在哪个服务器

如果某个服务器down了,需要知道哪个从队列被选择成为继任的主队列。

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

这里会遇到一个问题,即怎么判断某个队列的主队列呢?一个方法是通过rabbitmqctl,如下面的例子:

./rabbitmqctl-pproductionlist_queuespidslave_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

相关推荐