1-MQ - RabbitMQ Cluster

一些概念

Installing on RPM-based Linux (RedHat Enterprise Linux, CentOS, Fedora, openSUSE)
Classic Mirrored Queues

采用集群的目的

RabbitMQ本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的erlang.cookie来实现)。因此,RabbitMQ天然支持集群。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。

集群的配置方式
RabbitMQ可以通过三种方法来部署分布式集群系统,分别是:

  • cluster:不支持跨网段,用于同一个网段内的局域网;可以随意的动态增加或者减少;;节点之间需要运行相同版本的RabbitMQ和Erlang。
  • federation:应用于广域网,允许单台服务器上的交换机或队列接收发布到另一台服务器上交换机或队列的消息,可以是单独机器或集群。federation队列类似于单向点对点连接,消息会在联盟队列之间转发任意次,直到被消费者接受。通常使用federation来连接internet上的中间服务器,用作订阅分发消息或工作队列。
  • shovel:连接方式与federation的连接方式类似,但它工作在更低层次。可以应用于广域网。

RabbitMQ Cluster集群同步原理

上图是采用了三个节点的的RabbitMQ集群,Exchange A的元数据信息在所有的节点上是一致的,而Queue的完整数据则只会存放在创建它的节点上。其他节点只存储这个Queue的metadata信息和一个指向Queue的主节点(owner node)的指针。

RabbitMQ集群元数据的同步

RabbitMQ集群会始终同步四种类型的内部元数据(类似索引):

  • 队列元数据:队列名称和它的属性。
  • 交换器元数据:交换器名称、类型和属性。
  • 绑定元数据:一张简单的表格展示了如何将消息路由到队列。
  • vhost元数据:为vhost内的队列、交换器和绑定提供命名空间和安全属性;因此,当用户访问其中任何一个RabbitMQ节点时,通过rabbitmqctl查询到的queue、user、exchange、vhost等信息都是相同的。

为何RabbitMQ集群仅采用元数据同步的方式?

  • 存储空间,如果每个集群节点都拥有所有Queue的完全数据拷贝,那么每个节点的存储空间会非常大,集群的消息积压能力会非常弱(无法通过集群节点的扩容提高消息积压能力)。
  • 性能,消息的发布者需要将消息复制到每一个集群节点,对于持久化消息,网络和磁盘同步复制的开销都会明显增加。

RabbitMQ集群发送/订阅消息的基本原理

场景1:客户端直接连接队列所在节点

如果有一个消息生产者或者消息消费者通过amqp-client的客户端连接至节点1进行消息的发布或者订阅,那么此时的集群中的消息收发只与节点1相关,这个没有任何问题;如果客户端相连的是节点2或者节点3(队列1数据不在该节点上),那么情况又会是怎么样呢?

场景2:客户端连接的是非队列数据所在节点

如果消息生产者所连接的是节点2或者节点3,此时队列1的完整数据不在这两个节点上,那么在发送消息过程中这两个节点主要起了一个路由转发作用,根据这两个节点上的元数据(也就是上文提到的:指向queue的owner node的指针)转发至节点1上,最终发送的消息还是会存储至节点1的队列1上。
同样,如果消息消费者所连接的节点2或者节点3,那这两个节点也会作为路由节点起到转发作用,将会从节点1的队列1中拉取消息进行消费。

根据上述的原理,RabbitMQ集群有两种工作模式:

  • 普通模式:当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer,所以consumer应平均连接每一个节点,从中取消息。该模式存在一个问题就是当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了队列持久化或消息持久化,那么得等A节点恢复,然后才可被消费,并且在A节点恢复之前其它节点不能再创建A节点已经创建过的持久队列;如果没有持久化的话,消息就会失丢。这种模式更适合非持久化队列,只有该队列是非持久的,客户端才能重新连接到集群里的其他节点,并重新创建队列。假如该队列是持久化的,那么唯一办法是将故障节点恢复起来。为什么RabbitMQ不将队列复制到集群里每个节点呢?这与它的集群的设计本意相冲突,集群的设计目的就是增加更多节点时,能线性的增加性能(CPU、内存)和容量(内存、磁盘)。当然RabbitMQ新版本集群也支持队列复制(有个选项可以配置)。比如在有五个节点的集群里,可以指定某个队列的内容在2个节点上进行存储,从而在性能与高可用性之间取得一个平衡(应该就是指镜像模式)。
  • 镜像模式:其实质和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在consumer取数据时临时拉取。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用.

节点类型

  • RAM Node:内存节点将所有的队列、交换机、绑定、用户、权限和vhost的元数据定义存储在内存中,好处是可以使得像交换机和队列声明等操作更加的快速。
  • Disk Node:将元数据存储在磁盘中,单节点系统只允许磁盘类型的节点,防止重启RabbitMQ的时候,丢失系统的配置信息。

不同的节点类型在在web管理界面中有体现:

如果是内存结点这里就显示为RAM,磁盘节点显示为disk。
注意:

  • RabbitMQ要求在集群中至少有一个磁盘节点,所有其他节点可以是内存节点,当节点加入或者离开集群时,必须要将该变更通知到至少一个磁盘节点。
  • 如果集群中唯一的一个磁盘节点崩溃的话,集群仍然可以保持运行,但是无法进行其他操作(包括创建队列、交换器、绑定,添加用户、更改权限、添加和删除集群结点),直到节点恢复。
  • 解决方案:设置两个磁盘节点,至少有一个是可用的,可以保存元数据的更改。

Erlang Cookie

Erlang Cookie是保证不同节点可以相互通信的密钥,要保证集群中的不同节点相互通信必须共享相同的Erlang Cookie。具体的目录存放在/var/lib/rabbitmq/.erlang.cookie

接下来的演示基于docker部署rabbitmq集群。

普通模式集群的搭建

先来看如何搭建普通模式的集群。

这里的案例是搭建一个四个节点的RabbitMQ集群,各有两个RAM和DISK节点。

启动各节点容器

# 拉取镜像
docker pull rabbitmq:management

# ------------ mq1 -------------
docker run \
-d \
--name rabbitmq1 \
--hostname mq1 \
--restart=always \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=12346 \
-v /data/rabbitmq_data/mq1:/var/lib/rabbitmq \
-p 5673:5672 \
-p 15673:15672 \
rabbitmq:management

# ------------ mq2 -------------
docker run \
-d \
--name rabbitmq2 \
--hostname mq2 \
--restart=always \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=12346 \
--link rabbitmq1:mq1 \
-v /data/rabbitmq_data/mq2:/var/lib/rabbitmq \
-p 5674:5672 \
-p 15674:15672 \
rabbitmq:management

# ------------ mq3 -------------
docker run \
-d \
--name rabbitmq3 \
--hostname mq3 \
--restart=always \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=12346 \
--link rabbitmq1:mq1 \
--link rabbitmq2:mq2 \
-v /data/rabbitmq_data/mq3:/var/lib/rabbitmq \
-p 5675:5672 \
-p 15675:15672 \
rabbitmq:management

# ------------ mq4 -------------
docker run \
-d \
--name rabbitmq4 \
--hostname mq4 \
--restart=always \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=12346 \
--link rabbitmq1:mq1 \
--link rabbitmq2:mq2 \
--link rabbitmq3:mq3 \
-v /data/rabbitmq_data/mq4:/var/lib/rabbitmq \
-p 5676:5672 \
-p 15676:15672 \
rabbitmq:management

需要注意的是:多个容器之间使用--link进行连接,此属性不能少。

确认各节点的.erlang.cookie文件的一致性

现在,各节点顺利启动了,但是各节点的.erlang.cookie并不一定是一致的,如果出现各节点.erlang.cookie不一致情况,则后续的组建集群就会失败,所以要检查并确认.erlang.cookie的一致性:

# 如果是docker容器搭建的集群, .erlang.cookie 查看方式
[root@cs ~]# docker exec -it rabbitmq1 bash
root@mq1:/# cat /var/lib/rabbitmq/.erlang.cookie
ZMOLTOWMHAWHIEQMJSJP

同步各节点的.erlang.cookie文件,你可能用到的命令:

# 将容器中指定目录下的文件拷贝到宿主机的指定目录中
docker cp 容器名:容器内文件路径 宿主机目录
[root@cs ~]# docker cp rabbitmq1:/var/lib/rabbitmq/.erlang.cookie /tmp

# 将宿主机中的文件拷贝到容器中指定目录下
docker cp 宿主机文件路径 容器名:容器内文件路径
[root@cs ~]# docker cp /tmp/.erlang.cookie rabbitmq2:/var/lib/rabbitmq/.erlang.cookie

注意,同步完各节点的.erlang.cookie后,需要重启容器使之加载最新的.erlang.cookie

配置各节点加入集群

# ------------ mq1 -------------
# 配置mq1为disk节点
docker exec -it rabbitmq1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit

# ------------ mq2 -------------
# 配置mq2为disk节点
docker exec -it rabbitmq2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@mq1
rabbitmqctl start_app
exit

# ------------ mq3 -------------
# 配置mq3为RAM节点
docker exec -it rabbitmq3 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@mq1
rabbitmqctl start_app
exit

# ------------ mq4 -------------
# 配置mq4为RAM节点
docker exec -it rabbitmq4 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@mq1
rabbitmqctl start_app
exit

参数--ram表示设置为内存节点,忽略此参数默认为磁盘节点。

现在,web管理界面就有了这四个节点的信息:

镜像队列集群

镜像队列是基于普通的集群模式的,然后再添加一些策略,所以还是得先配置普通集群,然后才能设置镜像队列。镜像队列存在于多个节点。要实现镜像模式,需要先搭建一个普通集群模式,在这个模式的基础上再配置镜像模式以实现高可用。

镜像队列的相关概念

镜像队列原理

默认的一个rabbitmq中的queue是在一个node上的,至于在那个node上取决于client 进行declare的时候的顺序,但rabbitmq中的exchange、bindings都是需要在全部的节点上存在的,也就是rabbitmq集群天生就是支持自动同步这些信息的。而queue则是可以通过mirrored 同步到多个节点上,至于到底是几个节点可以指定。

PS:rabbitmq集群中的queue不是没有同步,是有同步的,只是他们同步的只是queue的元信息,在之前介绍中有说明。

每一个queue都会创建在一个master node或者多个slaves node上 ,并且当master节点丢失的时候,最老的slaves节点将会变成最新的master,当然这有一个前提那就是要求slave节点必须已经同步了master 节点的内容,如果没有同步的话那么这个slave 节点是不可以成为master 节点的。更多细节涉及到了主节点的选举机制了,这里不再过多介绍。

rabbitmq的集群和镜像队列区别

rabbitmq的集群是根据erlang的同步机制来实现的,这是erlang自带的功能,十分强大,如果几个rabbitmq node进行了集群后,这些node之前是可以同步元数据的,包括exchange的元数据、队列的元数据、binding的元数据,但是需要注意的是不会包括同步queue的内容默认情况下。主要是因为如果同步的queue的内容的话,如果出现了queue很多的时候,并且每一个queue的内容都很大的时候,rabbitmq就仅仅需要忙着处理这些本node的queue还要负责处理和不同的node之前进行同步,这会造成系统很大负载,对rabbitmq的整体性能会有很多的影响,因此默认是关闭的。那么如果你非要进行队列的内容同步,比如你的队列吞吐量并不高,每一个队列的内容也不大,对性能不会造成什么影响,那么就可以打开镜像队列,这个时候集群之前就会同步queue的内容了。

镜像队列其实就是原来channel只会按照bindings来把消息路由给某个队列,现在如果你使用了镜像队列,那么channel就需要并行的把消息路由给master node 同时还需要把消息同步给slave node上,所以当使用镜像队列以后性能是一定会下降的。

镜像队列实现

摘自:RabbitMQ集群原理介绍

镜像队列基本上就是一个特殊的BackingQueue,它内部包裹了一个普通的BackingQueue做本地消息持久化处理,在此基础上增加了将消息和ack复制到所有镜像的功能。所有对mirror_queue_master的操作,会通过可靠组播GM的方式同步到各slave节点。GM负责消息的广播,mirror_queue_slave负责回调处理,而master上的回调处理是由coordinator负责完成。mirror_queue_slave中包含了普通的BackingQueue进行消息的存储,master节点中BackingQueue包含在mirror_queue_master中由AMQQueue进行调用。

消息的发布与消费都是通过master节点完成。master节点对消息进行处理的同时将消息的处理动作通过GM广播给所有的slave节点,slave节点的GM收到消息后,通过回调交由mirror_queue_slave进行实际的处理。

GM(Guarenteed Multicast)

GM模块实现的一种可靠的组播通讯协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。

它的实现大致如下:

将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管保证本次广播的消息会复制到所有的节点。在master节点和slave节点上的这些gm形成一个group,group(gm_group)的信息会记录在mnesia中。不同的镜像队列形成不同的group。消息从master节点对于的gm发出后,顺着链表依次传送到所有的节点,由于所有节点组成一个循环链表,master节点对应的gm最终会收到自己发送的消息,这个时候master节点就知道消息已经复制到所有的slave节点了。

queue的master node和slave node

每一个queue都有一个home node也叫主队列。所有的镜像操作首先必须要通过这个master,然后在复制到其他的mirrors上,这样做主要是为了保证消息的FIFO顺序,也就是说所有顺序执行的操作都要顺序交给master,然后channel在同步给slave node,如果master上移出了队列中的内容,那么所有的slave node也同步的移出所有的队列内容。但是要记住consumer消费的只是 master node,并不是slave node。

可以通过如下的策略来决定 queue master 在那个节点上:

  • 选择存在主队列 最少的的节点: min-masters
  • 选择client 声明queue 连接的节点:client-local
  • 随机选择:random

以上策略的配置可以通过如下几个方法:

  • 在创建queue的时候使用x-queue-master-locator来指明用上面的哪个策略
  • 在配置文件中使用配置项 queue_master_locator指明使用上面的哪个策略

新节点同步机制

如果集群中新加了一个node,那么这个node就会变成备复制节点,而且备复制节点不会主动的复制master node 上的queue的内容,当然了他是会同步master node上的queue的元数据的,那么怎么实现和master node上的queue内容同步呢?这就需要随着时间的推移一点一点的来同步了,也就是说新加入的节点只会同步后来加入到master node上的队列内容并且随着master node上的queue里面的内容被不断的消费掉,很快备份节点和master node上面的内容就会一样了。

但是考虑到万一consumer正好发布消息到了新的node上,并且master node 又挂了会怎么样呢?答案是会丢失消息。因此在master node 将现存的queue的内容复制到其他的新节点之前,分辨是否所有从copy和master node拥有相同的内容就很重要了。为了检测镜像队列同步的状态,可以使用如下的命令来确定:

rabbitmqctl list_queues pid slave_pids synchronised_slave_pids

[root@cs ~]# docker exec -it rabbitmq1 bash
root@mq1:/# rabbitmqctl list_queues pid slave_pids synchronised_slave_pids
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
            pid	                         slave_pids	                     synchronised_slave_pids
<rabbit@mq1.1604472735.7876.0>	[<rabbit@mq3.1604475591.2393.0>]	[<rabbit@mq3.1604475591.2393.0>]
<rabbit@mq1.1604472735.7872.0>		
<rabbit@mq1.1604472735.8124.0>	[<rabbit@mq2.1604474711.3278.0>]	[<rabbit@mq2.1604474711.3278.0>]

这个命令会显示出 slave-pidssynchronised_slave_pids

如果两个是一样的时候就表示所有的队列内容同步完成了,否则就是还没有同步完成,这个时候master node是一定不可以删除的。

镜像队列的策略介绍

Classic Mirrored Queues

这里通过使用policy模块在普通集群的基础来完成镜像队列配置。

rabbitmqctl set_policy ha-all "^" '{"ha-mode": "all"}'

上述语句可以在集群中任意节点创建并启用策略,策略会自动同步到集群节点,其中:

  • ha-all为策略名称
  • "^"表示匹配所有,"^logs"表示匹配名称为以logs开头的Queue。
  • ha-mode为匹配类型,它的值有三种类型:
    • all:所有,表示为匹配集群中所有Queue。
    • exctly:部分,需要搭配ha-params参数使用,该ha-params参数的值为int类型,如ha-params:2表示集群中任意的2个Node节点上的Queue。
    • nodes:指定,需要搭配ha-params参数使用,该ha-params参数的值为数组类型,如ha-params:["rabbit@mq1", "rabbit@mq2"]表示集群中mq1mq22个Node节点上的Queue。

一些示例:

# 声明一个策略名为 ha-all 的策略,匹配名称以 "ha" 开头的队列,并将镜像配置到集群中所有的节点
rabbitmqctl set_policy ha-all "^" '{"ha-mode": "all"}'

# 声明一个策略名为 ha-two 的策略,匹配名称以 "two." 开头的队列被镜像到集群中的任意两个节点,并实现自动同步
rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

# 声明一个策略名为 ha-nodes 的策略,匹配名称以 "nodes." 开头的队列被镜像到集群中的特定节点
rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'

其中,官网也告诉我们:

Note that mirroring to all nodes is rarely necessary and will result in unnecessary resource waste.
Consider mirroring to the majority (N/2+1) nodes with "ha-mode":"exactly" instead.

大致意思是没必要将Queue镜像到所有的节点,这会导致不必要的资源浪费;并建议使用"ha-mode":"exctly"模式,将Queue镜像到(N/2+1,即集群的半数加一)个节点上。

镜像队列的策略实战

制定镜像队列的策略
使用下面的策略,来配置集群的镜像队列:

# 声明一个策略名为 ha-two 的策略,匹配名称以 "two." 开头的队列被镜像到集群中的任意两个节点,并实现自动同步
rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

实施策略
后台执行策略:

[root@cs ~]# docker exec -it rabbitmq1 bash
root@mq1:/# rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
Setting policy "ha-two" for pattern "^two\." to "{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}" with priority "0" for vhost "/" ...

然后创建几个队列:

import pika

# 连接 RabbitMQ Server
credentials = pika.PlainCredentials('guest', '12346')  # mq用户名和密码
connection = pika.BlockingConnection(
    pika.ConnectionParameters(
        host='192.168.10.91',
        port=5673,
        virtual_host='/',
        credentials=credentials)
)

# 生产者通过 channel 对象与 RabbitMQ Server 打交道
channel = connection.channel()

# 声明一个队列,如果该队列不存在就创建
channel.queue_declare(queue='news1')
channel.queue_declare(queue='two.logs-info')
channel.queue_declare(queue='two.logs-warning')

connection.close()

在web管理界面的Queues选项中就可以看到效果了:

我们也可以在Admin栏对策略进行管理:

PS:点击策略名称也可以删除策略。

RabbitMQ3.8.0版本新队列:仲裁队列

仲裁队列(Quorum Queues)是RabbitMQ3.8.0版本的新的队列类型,它基于Raft共识算法实现的持久的FIFO队列。

仲裁队列类型是持久的镜像队列的一种替代方法,主要目标是解决集群中的数据安全。

由于仲裁队列是新版本功能,其稳定性也有待考证,所以这里不在多表,详情参考官网:Quorum Queues

相关命令

在集群的搭建过程中,我们也接触到了一些命令,这里列出其他命令供参考。

# 查看所有队列信息
rabbitmqctl list_queues

# 关闭应用(关闭当前启动的节点)
rabbitmqctl stop_app

# 启动应用,和上述关闭命令配合使用,达到清空队列的目的
rabbitmqctl start_app

# 从管理数据库中移除所有数据,例如配置过的用户和虚拟宿主, 删除所有持久化的消息(这个命令要在rabbitmqctl stop_app之后使用)
rabbitmqctl reset

# 作用和rabbitmqctl reset一样,区别是无条件重置节点,不管当前管理数据库状态以及集群的配置。如果数据库或者集群配置发生错误才使用这个最后的手段
rabbitmqctl force_reset

# 从集群中移除节点,首先要停止节点后才能移除
docker exec -it rabbitmq2 bash
rabbitmqctl stop_app
rabbitmqctl --node rabbit@mq2 reset

# 查看节点状态
rabbitmqctl status

# 添加用户
rabbitmqctl add_user username password

# 列出所有用户
rabbitmqctl list_users

# 列出用户权限
rabbitmqctl list_user_permissions username

# 修改密码
rabbitmqctl change_password username newpassword

# 创建虚拟主机
rabbitmqctl add_vhost vhostpath

# 列出所有虚拟主机
rabbitmqctl list_vhosts

# 设置用户权限
rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"

# 列出虚拟主机上的所有权限 
rabbitmqctl list_permissions -p vhostpath

# 清除用户权限
rabbitmqctl clear_permissions -p vhostpath username

# 清除队列里的消息
rabbitmqctl -p vhostpath purge_queue blue

# 删除用户
rabbitmqctl delete_user username

# 删除虚拟主机
rabbitmqctl delete_vhost vhostpath

参考:RabbitMQ学习笔记四:RabbitMQ命令(附疑难问题解决)

question

节点停止失败

docker环境搭建集群

在将各个节点启动后,需要将节点组成集群时,需要先停止,然后加如集群,在停止时遇到问题了:

报错信息
[root@cs ~]# docker exec -it rabbitmq2 bash
root@mq2:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@mq2 ...
Error: unable to perform an operation on node 'rabbit@mq2'. Please see diagnostics information and suggestions below.

Most common reasons for this are:

 * Target node is unreachable (e.g. due to hostname resolution, TCP connection or firewall issues)
 * CLI tool fails to authenticate with the server (e.g. due to CLI tool's Erlang cookie not matching that of the server)
 * Target node is not running

In addition to the diagnostics info below:

 * See the CLI, clustering and networking guides on https://rabbitmq.com/documentation.html to learn more
 * Consult server logs on node rabbit@mq2
 * If target node is configured to use long node names, don't forget to use --longnames with CLI tools

DIAGNOSTICS
===========

attempted to contact: [rabbit@mq2]

rabbit@mq2:
  * connected to epmd (port 4369) on mq2
  * epmd reports node 'rabbit' uses port 25672 for inter-node and CLI tool traffic 
  * TCP connection succeeded but Erlang distribution failed 
  * suggestion: check if the Erlang cookie identical for all server nodes and CLI tools
  * suggestion: check if all server nodes and CLI tools use consistent hostnames when addressing each other
  * suggestion: check if inter-node connections may be configured to use TLS. If so, all nodes and CLI tools must do that
   * suggestion: see the CLI, clustering and networking guides on https://rabbitmq.com/documentation.html to learn more


Current node details:
 * node name: 'rabbitmqcli-754-rabbit@mq2'
 * effective user's home directory: /var/lib/rabbitmq
 * Erlang cookie hash: wAcG6R9I7xf1cvxpsW8W9A==

我首先确认各节点的.erlang.cookie文件是否一致:

[root@cs ~]# docker exec -it rabbitmq1 bash
root@mq1:/# cat /var/lib/rabbitmq/.erlang.cookie 
ZMOLTOWMHAWHIEQMJSJProot@mq1:/# exit
exit
[root@cs ~]# docker exec -it rabbitmq2 bash
root@mq2:/# cat /var/lib/rabbitmq/.erlang.cookie
ZMOLTOWMHAWHIEQMJSJProot@mq2:/# exit
exit

发现两个节点的.erlang.cookie是一致的,但跟报错中的.erlang.cookie却不一致,经过思考,这原因是docker run启动节点时,使用的是当时生成的.erlang.cookie,而同步各节点的.erlang.cookie是启动节点之后的操作。这就导致虽然同步了.erlang.cookie,但节点处于运行状态,没有利用上最新的.erlang.cookie导致停止失败。解决办法就是重启该节点,使之加载最新的.erlang.cookie

[root@cs ~]# docker restart rabbitmq2
rabbitmq2
[root@cs ~]# docker exec -it rabbitmq2 bash
root@mq2:/# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@mq2 ...

that's all, see also:

python – 获取RabbitMQ队列中的消息数 | python监控rabbitmq的消息队列数量 | Docker实战:Docker安装部署RabbitMQ | MQ - RabbitMQ - 架构及工作原理 | docker 安装rabbitMQ | 理解 RabbitMQ Exchange | RabbitMQ系列教程(六)RabbitMQ Exchange类型之headers Exchange | 消息队列 | python - pika模块的使用中,如何动态的删除一个 durable=True 的持久化队列? | rabbitMq集群之镜像模式 | Rabbitmq集群和镜像队列 | RabbitMQ集群原理介绍

posted @ 2020-11-04 10:50  听雨危楼  阅读(1558)  评论(0编辑  收藏  举报