阅读 1340

RabbitMQ系列(十)分布式RabbitMQ(集群)

概述

RabbitMQ集群是一个或多个节点的逻辑分组,每个节点共享用户、虚拟主机、队列、交换机、绑定、运行时参数和其他分布式状态。

本指南涵盖了RabbitMQ集群的基本主题:
1、RabbitMQ节点如何标识:节点名称;
2、集群的要求;
3、集群节点间哪些数据可以复制,哪些不可以复制;
4、集群对于客户端意味着什么;
5、集群如何形成;
6、节点间如何验证双方(包括CLI工具);
7、节点重启以及如何重新加入到集群中;
8、如何重置集群节点;
更多...
集群的形成以及对等点发现是密切相关的指南; 对于队列内容(消息)复制,请查看镜像队列指南;

其中,元数据包括:

  1. 虚拟主机元数据;
  2. 交换机元数据;
  3. 队列元数据;
  4. 绑定元数据;
  5. 用户元数据;
  6. 权限元数据;

注意:

  1. 队列与队列中的消息只在创建它的服务器节点(如节点1)上,其他节点只是存储其元数据及执行队列所在的服务器节点指针;
  2. 客户端直接连接到队列所在服务器节点(如节点1)时对队列进行消息发布或订阅等操作时,那么集群中的消息只与队列所在服务器节点(如节点1)有关;
  3. 客户端连接到非队列所在服务器节点(如节点2或者3)时对队列进行消息发布或订阅等操作时,此时队列的完整数据不在这两个节点上(如节点2或者3),那么这两个节点主要起了一个路由转发作用,根据这两个节点上的元数据转发至队列所在服务器节点上(如节点1),最终发送的消息还是会存储至队列所在服务器节点上(如节点1)。同样,如果消息消费者所连接的非队列所在服务器节点(如节点2或者3),那这两个节点也会作为路由节点起到转发作用,将会从队列所在服务器节点中(如节点1)拉取消息进行消费。
  4. 普通集群模式,并不保证队列的高可用性。尽管交换机、绑定这些可以复制到集群里的任何一个节点,但是队列内容不会复制。虽然该模式解决节点压力,但队列节点宕机直接导致该队列无法应用,只能等待重启。
    所以要想在队列节点宕机或故障也能正常应用,就要复制队列内容到集群里的每个节点,也就是必须要创建镜像队列

集群的形成

集群的形成方式

一个RabbitMQ集群可以有多种形成方式:
1)通过在配置文件中列出集群节点来声明;
2)通过使用基于DNS发现来声明;
3)通过使用AWS(EC2)实例发现来声明(通过插件);
4)通过使用Kubernetes发现来声明(通过插件);
5)通过基于Consul发现来声明(通过插件);
6)通过基于etcd发现来声明(通过插件);
7)手动rabbitmqctl工具;
请参考集群形成指南获取详细信息。

集群的组成可以动态修改。所有的RabbitMQ代理一开始都运行在单个节点上。这些节点可以加入集群并可重新退出集群作为独立的消息代理。

节点名称(标识)

RabbitMQ节点通过节点名称来标识。一个节点名称由两部分组成:前缀和主机名。例如rabbit@node1.messaging.svc.local,前缀为rabbit,主机名为node1.messaging.svc.local;
当一个节点启动时,它将检查是否被赋予一个节点名称。默认情况下,将使用前缀“rabbit”和所在主机的主机名作为它的节点名称。

  1. 在集群中,节点或者CLI工具是使用节点名称进行标识和联系。这意味着每个节点的主机名必须是可解析的;
  2. 节点名称在集群中必须唯一。如果在一台主机中运行超过一个节点(通常在开发或QA环境下),它们必须使用不同的前缀。如rabbit1@hostname和rabbit2@hostname;
  3. 自定义节点名称:
    通过RABBITMQ_NODENAME环境变量进行自定义;如果一个系统使用完整的域名作为主机名,RabbitMQ节点和CLI工具必须配置使用长节点名称。对于服务节点,可以通过设置RABBITMQ_USE_LONGNAME环境变量为true来实现。 对于CLI工具,可以设置RABBITMQ_USE_LONGNAME或者指定--longnames选项。

集群形成的要求

主机名解析

RabbitMQ节点使用域名(短的或者完整指定)相互定位对方。因此,集群中的所有成员的主机名必须是可解析的,以及集群中的机器可以使用rabbitmqctl等命令行工具。
主机解析可以使用标准操作系统提供的任何方法:本地主机文件(如/etc/hosts)的DNS记录。

在更多限制环境下,DNS记录和主机文件修改是受限的,不可能或者不可取的,可以将Erlang VM配置为使用备用的主机名解析方法,例如备用DNS服务器、本地文件、非标准主机文件位置或方法组合。这些方法可以与标准的OS主机名解析方法协同工作。

端口访问

RabbitMQ服务节点绑定端口(开放服务器TCP套接字),为了接收客户端、CLI工具和RabbitMQ其他节点的连接。其他进程及工具(如SELinux)或者防火墙可能阻止RabbitMQ绑定到一个端口上。如果出现这种情况,节点将启动失败。因此,确保如下端口可访问:

端口 描述
4369 epmd进程使用的端口。用于RabbitMQ节点和CLI工具的端点发现服务。
5671,5672 TLS或无TLS的AMQP 0-9-1以及AMQP 1.0客户端所使用。
15672 HTTP API客户端、管理UI以及rabbitmqadmin所使用。
只有management插件启用才生效。
25672 RabbitMQ节点间或CLI工具通信所使用的端口(Erlang分布式服务端口)。
默认限制为单个端口,一般为AMQP端口+20000。 除非外部连接需要,否则该端口不应该暴露到外部。
35672~35682 CLI工具与RabbitMQ节点之间 的通信(Erlang分布式服务端口)。
一般为上述分布式端口+10000~10010。
61613,61614 无TLS和TLS的STOMP客户端所使用。
只有STOMP插件启用才生效。
1883,8883 无TLS和TLS的MQTT客户端所使用。
只有MQTT插件启用才生效。
15674 基于WebSockets之上的STOMP客户端所使用。
只有Web STOMP插件启用才生效。
15675 基于WebSockets之上的MQTT客户端所使用。
只有Web MQTT插件启用才生效。
15692 Prometheus指标监控所使用。
只有Prometheus插件启用才生效。

集群中的节点

什么是复制

RabbitMQ代理操作所需的所有数据/状态被复制到所有节点。但消息队列除外,它默认驻留在一个节点上,尽管它们是可见的且可以从所有节点获取到。
要在集群中跨节点复制队列,请参阅关于高可用性的文档。

注意:本指南是镜像的先决条件。

节点是对等点

一些分布式系统有Leader和Follower节点,但这通常不适用于RabbitMQ。RabbitMQ集群中的所有节点是对等点:在RabbitMQ核心没有特殊节点。 独立插件可以在一段时间内指定(选择)某些节点为“特殊”节点。例如,联合链接在特定的集群节点上进行协作。如果该节点失败,将在另一个节点上重新启动连接。
CLI工具操作可以在任何一个节点上执行。HTTP API客户端可以针对任何集群节点。

在RabbitMQ 3.6.7以上的版本中,RabbitMQ管理插件使用专用节点来收集和聚合数据。

CLI工具如何对节点(以及节点之间)进行身份验证:Erlang Cookie

RabbitMQ节点和CLI工具(如rabbitmqctl)使用Cookie来决定是否允许相互通信。两个节点要能相互通信,它们必须要有相同的共享密钥(Erlang Cookie)。该Cookie只是一个由255个字符组成的字符串。它通常存储在本地文件中。该文件必须只能由所有者访问(例如,UNIX权限为600或类似)。集群中的每个节点必须具有相同的Cookie。如果该文件不存在,在RabbitMQ服务器启动时,Erlang VM将试图创建一个含随机数的文件。使用这样生成的Cookie文件只适用于开发环境。由于每个节点都将独立地生成自己的值,因此这种策略在集群环境中并不真正可行。

Erlang Cookie的生成应该在集群部署阶段完成,最好使用自动化和编制工具,如Chef、Puppet、BOSH、Docker或类似的工具。

Cookie文件路径

Linux, MacOS, *BSD

在UNIX系统中,Cookie文件通常位于/var/lib/rabbitmq/.erlang.cookie(由RabbitMQ服务器使用)以及$HOME/.erlang.cookie(由CLI工具使用)。

注意:由于$HOME变量(用户HOME目录)的只因人而异,有必要为每个将使用CLI工具的用户放置cookie文件的副本。这适用于非特权用户和根用户。

Windows

在Windows系统中,Cookie的位置取决于几个因素:
1)Erlang version:在20.2之前或者20.2及之后;
2)是否同时设置了HOMEDRIVE和HOMEPATH环境变量;

在Erlang 20.2及之后,Cookie文件位置如下:

1)如果HOMEDRIVE和HOMEPATH环境变量都设置:
%HOMEDRIVE%%HOMEPATH%\.erlang.cookie(通常是C:\Users\%USERNAME%\.erlang.cookie,对于%USERNAME%用户)。
2)如果HOMEDRIVE和HOMEPATH环境变量都没设置:
%USERPROFILE%\.erlang.cookie(通常是C:\Users\%USERNAME%\.erlang.cookie);
3)对于RabbitMQ的Windows服务-%USERPROFILE%\.erlang.cookie(通常是C:\WINDOWS\system32\config\systemprofile)。
复制代码

如果使用Windows服务,Cookie应该从C:\Windows\system32\config\systemprofile.erlang.cookie拷贝到运行rabbitmqtl.bat等命令的用户的预期位置。

Erlang 19.3到20.2,Cookie文件的位置是:

1)如果HOMEDRIVE和HOMEPATH环境变量都设置:
%HOMEDRIVE%%HOMEPATH%\.erlang.cookie(通常是C:\Users\%USERNAME%\.erlang.cookie,对于%USERNAME%用户)。
2)如果HOMEDRIVE和HOMEPATH环境变量都设置:
%USERPROFILE%\.erlang.cookie(通常是C:\Users\%USERNAME%\.erlang.cookie);
3)对于RabbitMQ的Windows服务-%WINDIR%\.erlang.cookie(通常是C:\Windows\.erlang.cookie)。
复制代码

如果使用Windows服务,Cookie应该从C:\Windows.erlang.cookie拷贝到运行rabbitmqtl.bat等命令的用户的预期位置。

故障排除

当节点启动时,它将记录其基目录位置。除非覆盖了任何服务器目录,否则RabbitMQ服务将在其中创建Cookie文件。

运行时参数

作为替代方案,你可以在RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS环境变量添加"-setcookie value"选项:RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie cookie-value"。
这是最不安全的选择,一般不推荐。

认证失败

如果Cookie配置错误(例如,不完全相同),RabbitMQ将记录错误(例如:"Connection attempt from disallowed node"和"Could not auto-cluster")。
如果CLI工具(如rabbitmqctl)认证失败,消息通常是这样的:

* epmd reports node 'rabbit' running on port 25672
* TCP connection succeeded but Erlang distribution failed
* suggestion: hostname mismatch?
* suggestion: is the cookie set correctly?
* suggestion: is the Erlang distribution using TLS?
复制代码

错误放置的Cookie文件或Cookie值不匹配是此类故障最常见的场景。

当使用最新的Erlang/OTP版本时,身份验证失败包含更多信息,而且可以更好地识别cookie不匹配:

* connected to epmd (port 4369) on warp10
* epmd reports node 'rabbit' running on port 25672
* TCP connection succeeded but Erlang distribution failed

* Authentication failed (rejected by the remote node), please check the Erlang cookie
复制代码

有关更多信息,请参阅CLI工具指南。

节点计数与仲裁

因为一些特性(例如quorum queue、MQTT中的客户端跟踪)需要集群成员之间达成一致,所以强烈建议使用奇数的集群节点:1、3、5、7等等。
强烈建议不要使用两个节点集群,因为在连接性丢失的情况下,集群节点不可能识别大多数节点并形成一致意见。例如,当两个节点失去连接时,MQTT客户机连接将不被接受,quorum队列将失去可用性,等等。 从一致的观点来看,4个或6个节点集群将具有与3个和5个节点集群相同的可用性特征。
Quorum queue指南更详细地介绍了这个主题。

集群和客户端

假设所有集群成员都可用,客户机可以连接到任何节点并执行任何操作。节点将透明地将操作路由到客户端队列主节点。
对于所有受支持的消息传递协议,客户机一次只连接到一个节点。在节点失败的情况下,客户端应该能够重新连接到不同的节点,恢复它们的拓扑并继续操作。因此,大多数客户端接收端点列表(主机名或IP地址)为连接选项。如果客户端支持,则在初始连接和连接恢复期间将使用主机列表。
在某些情况下,客户端可能无法在连接到其他节点后透明地继续操作。它们通常涉及托管在故障节点上的非镜像队列。

集群和可观测性

客户端连接、通道和队列将分布在集群节点上。操作员需要能够跨所有集群节点检查和监视这些资源。
RabbitMQ CLI工具(如RabbitMQ -diagnostics和rabbitmqctl)提供检查资源和集群范围状态的命令。一些命令关注单个节点的状态(例如rabbitmq-diagnostics环境和rabbitmq-diagnostics状态),其他命令则检查集群范围的状态。后者的一些例子包括rabbitmqctl list_connections、rabbitmqctl list_mqtt_connections、rabbitmqctl list_stomp_connections、rabbitmqctl list_users、rabbitmqctl list_vhosts等等。
这种“集群范围”的命令通常会首先联系一个节点,发现集群成员并联系所有成员来检索和组合它们各自的状态。例如,rabbitmqctl list_connections将联系所有节点,检索它们的AMQP 0-9-1和AMQP 1.0连接,并将它们全部显示给用户。用户不必手动联系所有节点。假设集群处于不变的状态(例如,没有关闭或打开任何连接),针对两个不同节点依次执行的两个CLI命令将产生相同或语义上相同的结果。然而,“Node-local”命令不会产生相同的结果,因为两个节点很少有相同的状态:至少它们的节点名是不同的!
管理UI的工作方式类似:响应HTTP API请求的节点将分散到其他集群成员并聚合它们的响应。在启用了管理插件的多个节点的集群中,操作员可以使用任何节点访问管理UI。使用HTTP API收集关于集群状态的数据的监视工具也是如此。不需要依次向每个集群节点发出请求。

节点失败处理

RabbitMQ代理可以容忍单个节点的失败。可以随意启动和停止节点,只要它们能够在关闭时联系到一个已知的集群成员节点。
队列镜像允许跨多个集群节点复制队列内容;非镜像队列也可以在集群中使用。节点失败时的非镜像队列行为取决于队列持久性。
RabbitMQ集群有几种处理网络分区的模式,主要是面向一致性的。集群意味着可以跨LAN使用。不建议运行跨越WAN的集群。Shovel或Shovel插件是跨WAN连接代理的更好的解决方案。

注意,Shovel和Federation插件并不等同于集群。

指标和统计

每个节点存储和聚合自己的指标和统计数据,并为其他节点提供API来访问它。一些统计信息是集群范围的,另一些是特定于单个节点的。响应HTTP API请求的节点与它的对等点联系以检索它们的数据,然后生成聚合结果。

在RabbitMQ 3.6.7以上的版本中,RabbitMQ管理插件使用专用节点来收集和聚合数据。

磁盘和RAM节点

节点可以是磁盘节点或RAM节点。
RAM节点只在RAM中存储内部数据库表。这并不包括消息、消息存储索引、队列索引和其他节点状态。

注意:磁盘和磁盘可以互换使用。 在绝大多数情况下,您希望所有节点都是磁盘节点;RAM节点是一种特殊情况,它可以用于提高具有高性能的队列、交换或绑定的集群。RAM节点不提供更高的消息速率。如果有疑问,只使用磁盘节点。

由于RAM节点只在RAM中存储内部数据库表,因此它们必须在启动时从对等点同步这些表。这意味着一个集群必须包含至少一个磁盘节点。因此,不能手动删除集群中最后一个剩余的磁盘节点。

集群的构建

下面是在三台机器(rabbit1、rabbit2、rabbit3)上手动设置和操作RabbitMQ集群的文本。建议在使用更适合自动化的集群形成选项之前对该示例进行研究。 我们假设用户登录到所有三台机器,RabbitMQ已经安装在机器上,rabbitmq-server和rabbitmqctl脚本位于用户的路径中。 可以将此脚本修改为在单个主机上运行,更多细节如下所示。

启动单个节点

集群是通过将现有RabbitMQ节点重新配置为集群配置来设置的。因此,第一步是在所有节点上以正常方式启动RabbitMQ:

# on rabbit1
rabbitmq-server -detached
# on rabbit2
rabbitmq-server -detached
# on rabbit3
rabbitmq-server -detached
复制代码

这将创建三个独立的RabbitMQ代理,每个节点上一个,由cluster_status命令确认:

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1]}]},{running_nodes,[rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit2]}]},{running_nodes,[rabbit@rabbit2]}]
# => ...done.

# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
# => ...done.
复制代码

从rabbitmq-server shell脚本启动的RabbitMQ代理的节点名是rabbit@shorthostname,其中短节点名是小写(如上面的rabbit@rabbit1)。
在Windows上,如果使用rabbitmq-server.bat批处理文件,则短节点名为大写(如rabbit@RABBIT1)。当您键入节点名时,大小写很重要,并且这些字符串必须完全匹配。

创建集群

为了将集群中的三个节点连接起来,我们让其中两个节点(如rabbit@rabbit2和rabbit@rabbit3)加入第三个节点(如rabbit@rabbit1)的集群。在此之前,必须重置两个新加入的成员。
我们首先将rabbit@rabbit2与rabbit@rabbit1联合在一个集群中。为此,我们在rabbit@rabbit2上停止RabbitMQ应用程序并加入rabbit@rabbit1集群,然后重新启动RabbitMQ应用程序。

请注意,必须重置节点才能加入现有集群。重置节点将删除该节点上以前存在的所有资源和数据。这意味着节点不能同时成为集群的成员并保留其现有数据。如果需要,可以使用蓝色/绿色部署策略或备份和恢复。

# on rabbit2
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit2 ...done.

rabbitmqctl reset
# => Resetting node rabbit@rabbit2 ...

rabbitmqctl join_cluster rabbit@rabbit1
# => Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.

rabbitmqctl start_app
# => Starting node rabbit@rabbit2 ...done.
复制代码

我们可以看到,这两个节点通过在其中一个节点上运行cluster_status命令连接到一个集群中:

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
# =>  {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
# => ...done.
复制代码

现在我们将rabbit@rabbit3加入到同一个集群中。上面的步骤是相同的,只不过这一次我们将集群rabbit2证明节点选择集群并不重要——这足以提供一个网络节点和节点将集群的集群节点属于指定。

# on rabbit3
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit3 ...done.

# on rabbit3
rabbitmqctl reset
# => Resetting node rabbit@rabbit3 ...

rabbitmqctl join_cluster rabbit@rabbit2
# => Clustering node rabbit@rabbit3 with rabbit@rabbit2 ...done.

rabbitmqctl start_app
# => Starting node rabbit@rabbit3 ...done.
复制代码

我们可以看到,这三个节点通过在任意节点上运行cluster_status命令连接到一个集群中:

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit3,rabbit@rabbit2,rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit3,rabbit@rabbit1,rabbit@rabbit2]}]
# => ...done.

# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit3,rabbit@rabbit2,rabbit@rabbit1]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit1,rabbit@rabbit3]}]
# => ...done.
复制代码

通过执行上述步骤,我们可以在集群运行时随时向集群添加新节点。

重启集群节点

接到集群的节点可以在任何时候停止。它们也可能失败或被操作系统终止。在所有情况下,集群的其余部分都可以继续运行,并且当其他集群节点重新启动时,节点会自动与它们“同步”。
注意,某些分区处理策略可能工作方式不同,并影响其他节点。 我们关闭节点rabbit@rabbit1和rabbit@rabbit3,每一步检查集群状态:

# on rabbit1
rabbitmqctl stop
# => Stopping and halting node rabbit@rabbit1 ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit3,rabbit@rabbit2]}]
# => ...done.

# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit3]}]
# => ...done.

# on rabbit3
rabbitmqctl stop
# => Stopping and halting node rabbit@rabbit3 ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit2]}]
# => ...done.
复制代码

现在我们再次启动节点,在运行过程中检查集群状态:

# on rabbit1
rabbitmq-server -detached
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
# => ...done.

# on rabbit3
rabbitmq-server -detached

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit1,rabbit@rabbit3]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]
# => ...done.

# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2,rabbit@rabbit3]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit1,rabbit@rabbit3]}]
# => ...done.
复制代码

了解流程节点在停止和重新启动时所经历的过程非常重要。

停止节点再重启后选择一个在线集群成员(只考虑磁盘节点)进行同步。在重新启动节点时,默认情况下将尝试与该对等点联系10次,并有30秒的响应超时。如果对等点在该时间间隔内可用,则节点将成功启动,与对等点同步所需内容并继续运行。如果对等节点不可用,重新启动的节点将放弃并自动停止。
当节点在关闭期间没有联机对等点时,它将在不尝试与任何已知对等点同步的情况下启动。但是,它不是作为一个独立的节点启动的,节点将能够重新加入它。
在节点名或主机名更改后重新连接的节点可以作为空白节点开始,如果其数据目录路径因此更改。这样的节点将无法重新加入集群。当节点脱机时,可以使用空白数据目录重置或启动其对等节点。在这种情况下,恢复节点也将无法重新加入其对等节点,因为内部数据存储集群标识将不再匹配。

考虑以下场景:
1)形成了一个由A、B和C三个节点组成的集群;
2)节点A关闭;
3)节点B复位;
4)节点A启动;
5)节点A试图重新加入B,但B的集群身份发生了变化;
6)节点B不承认A是已知的集群成员,因为它被重置了;

在这种情况下,节点B将拒绝A的集群尝试,并在日志中显示相应的错误消息: rabbit@node1.local节点认为它和节点rabbit@node2.local集群在一起,但rabbit@node2.local不同意。在这种情况下,可以再次重置B,然后将能够连接A,或者重置A,并将成功连接B。因此,当整个集群关闭时,最后一个关闭的节点是惟一一个在关闭时没有任何正在运行的节点。该节点可以在不首先联系任何节点的情况下启动。由于节点将尝试与已知的对等节点进行长达5分钟的连接(默认情况下),因此可以在这段时间内以任何顺序重新启动节点。在这种情况下,它们将一个接一个地成功地重新连接。这个时间窗口可以使用两个配置设置进行调整:

# wait for 60 seconds instead of 30
mnesia_table_loading_retry_timeout = 60000

# retry 15 times instead of 10
mnesia_table_loading_retry_limit = 15
复制代码

通过调整这些设置和调整已知端点必须返回的时间窗口,可以考虑集群范围内的重新部署场景,完成时间可能超过5分钟。
在升级期间,有时要停止的最后一个节点必须是升级后要启动的第一个节点。该节点将被指定执行集群范围的模式迁移,其他节点可以从中同步并在重新加入时应用。在某些情况下,最后一个脱机的节点无法恢复。可以使用get_cluster_node rabbitmqctl命令从集群中删除它。另外,可以在节点上使用force_boot rabbitmqctl命令来引导节点,而不必尝试与任何节点同步(就像它们最后一次关闭一样)。这通常只在最后一个要关闭的节点或一组节点永远不会重新联机时才有必要。

解除集群

有时需要从集群中删除节点。操作符必须使用rabbitmqctl命令显式地执行此操作。 一些对等点发现机制支持节点健康检查和强制删除发现后端不知道的节点。该功能是可选的(默认情况下禁用)。 我们首先从集群中删除rabbit@rabbit3,将其返回到独立操作。为此,我们在rabbit@rabbit3上停止RabbitMQ应用程序,重置节点,并重新启动RabbitMQ应用程序。

# on rabbit3
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit3 ...done.

rabbitmqctl reset
# => Resetting node rabbit@rabbit3 ...done.
rabbitmqctl start_app
# => Starting node rabbit@rabbit3 ...done.
复制代码

注意,将rabbit@rabbit3作为节点也同样有效。

在节点上运行cluster_status命令,确认rabbit@rabbit3现在不再是集群的一部分,并独立运行:

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
# => {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1,rabbit@rabbit2]}]},
# =>  {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
# => ...done.

# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
# => ...done.
复制代码

我们还可以远程删除节点,这很有用。例如,当必须处理一个没有响应的节点时。例如,我们可以从rabbit@rabbit2中删除rabbit@rabbi1。

# on rabbit1
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit1 ...done.

# on rabbit2
rabbitmqctl forget_cluster_node rabbit@rabbit1
# => Removing node rabbit@rabbit1 from cluster ...
# => ...done.
复制代码

注意,rabbit1仍然认为它与rabbit2聚集在一起,试图启动它会导致一个错误。我们将需要重置它,以便能够再次启动它。

# on rabbit1
rabbitmqctl start_app
# => Starting node rabbit@rabbit1 ...
# => Error: inconsistent_cluster: Node rabbit@rabbit1 thinks it's clustered with node rabbit@rabbit2, but rabbit@rabbit2 disagrees

rabbitmqctl reset
# => Resetting node rabbit@rabbit1 ...done.

rabbitmqctl start_app
# => Starting node rabbit@rabbit1 ...
# => ...done.
复制代码

cluster_status命令现在显示作为独立RabbitMQ代理运行的所有三个节点:

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1]}]},{running_nodes,[rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit2]}]},{running_nodes,[rabbit@rabbit2]}]
# => ...done.

# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
# => ...done.
复制代码

注意,rabbit@rabbit2保留了集群的剩余状态,而rabbit@rabbit1和rabbit@rabbit3是新初始化的RabbitMQ代理。如果我们想重新初始化rabbit@rabbit2,我们按照与其他节点相同的步骤:

# on rabbit2
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit2 ...done.
rabbitmqctl reset
# => Resetting node rabbit@rabbit2 ...done.
rabbitmqctl start_app
# => Starting node rabbit@rabbit2 ...done.
复制代码

除了rabbitmqctl forget_cluster_node和一些对等节点发现插件自动清除未知节点之外,RabbitMQ节点不会永久地从集群中删除其对等节点。

如何重置节点

有时可能需要重置一个节点(擦除它的所有数据),然后让它重新加入集群。一般来说,有两种可能的情况:当节点正在运行时,以及当节点不能启动或不能响应CLI工具命令时,例如,由于出现问题(如ERL-430)。
重置节点将删除其所有数据、集群成员关系信息、配置的运行时参数、用户、虚拟主机和任何其他节点数据。它还将从其集群中永久删除节点。
要重置一个正在运行和响应的节点,首先使用rabbitmqctl stop_app在其上停止RabbitMQ,然后使用rabbitmqctl reset对其进行重置:

# on rabbit1
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit1 ...done.
rabbitmqctl reset
# => Resetting node rabbit@rabbit1 ...done.
复制代码

对于没有响应的节点,必须首先使用任何必要的方法停止它。对于启动失败的节点,情况已经是这样了。然后覆盖节点的数据目录位置或[重新]移动现有数据存储。这将使节点以空白开始。如果有的话,必须指示它重新加入原来的集群。重置并重新加入原始集群的节点将同步所有虚拟主机、用户、权限和拓扑(队列、交换、绑定)、运行时参数和策略。它可以同步镜像队列的内容,如果选择托管一个副本。重置节点上的非镜像队列内容将丢失。在已从对等点同步其模式的重置节点上还原队列数据目录不能保证使该数据对客户端可用,因为受影响队列的队列主位置可能已更改。

升级集群

您可以在升级指南中找到升级集群的说明。

单台机器的集群

在某些情况下,在一台机器上运行RabbitMQ节点集群可能很有用。这对于在桌面或膝上型电脑上进行集群实验通常是有用的,而且不需要为集群启动多个虚拟机。 为了在一台机器上运行多个RabbitMQ节点,需要确保节点具有不同的节点名称、数据存储位置、日志文件位置,并绑定到不同的端口,包括插件使用的端口。请参阅配置指南中的RABBITMQ_NODENAME、RABBITMQ_NODE_PORT和RABBITMQ_DIST_PORT,以及文件和目录位置指南中的RABBITMQ_MNESIA_DIR、RABBITMQ_CONFIG_FILE和RABBITMQ_LOG_BASE。
可以通过重复调用rabbitmq-server(在Windows上的rabbitmq-server.bat)手动启动同一主机上的多个节点。例如:

RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit rabbitmq-server -detached
RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=hare rabbitmq-server -detached
rabbitmqctl -n hare stop_app
rabbitmqctl -n hare join_cluster rabbit@`hostname -s`
rabbitmqctl -n hare start_app
复制代码

将设置两个节点集群,两个节点都作为磁盘节点。注意,如果节点监听AMQP 0-9-1和AMQP 1.0以外的任何端口,则必须配置这些端口以避免冲突。这可以通过命令行:

RABBITMQ_NODE_PORT=5672 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15672}]" RABBITMQ_NODENAME=rabbit rabbitmq-server -detached
RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=hare rabbitmq-server -detached
复制代码

将在安装管理插件时启动两个节点(然后可以对其进行集群化)。

主机名变更

RabbitMQ节点使用主机名彼此通信。因此,所有节点名必须能够解析所有集群节点的名称。这也适用于rabbitmqctl等工具。
此外,默认情况下RabbitMQ使用系统的当前主机名来命名数据库目录。如果主机名更改,则创建一个新的空数据库。为了避免数据丢失,设置一个固定的、可解析的主机名至关重要。
每当主机名更改时,都必须重新启动RabbitMQ节点。
可以使用rabbit@localhost作为代理节点名来实现类似的效果。此解决方案的影响是集群无法工作,因为所选的主机名无法解析为来自远程主机的可路由地址。当从远程主机调用rabbitmqctl命令时,同样会失败。一个不受这个弱点影响的更复杂的解决方案是使用DNS,例如在EC2上运行的Amazon Route 53。如果您希望使用节点名的完整主机名(RabbitMQ默认为短名称),并且该完整主机名可以使用DNS解析,那么您可能需要研究设置环境变量RABBITMQ_USE_LONGNAME=true。
有关更多信息,请参阅有关主机名解析的部分。

防火墙的节点

当节点位于数据中心或可靠网络中,但被防火墙分隔时,就会出现防火墙集群节点。同样,在广域网上或节点之间的网络链接不可靠时,不建议使用集群。
在上面的端口一节和专用RabbitMQ网络指南中了解更多信息。

跨集群的Erlang版本

集群中的所有节点必须运行相同的小版本Erlang: 21.3.4和21.3.6可以混合使用,但21.0.1和21.3.6(或20.3和22.0.6)不能混合使用。不同版本的Erlang/OTP补丁之间的兼容性可能会有所不同,但通常很少出现这种情况。

从客户端连接到集群

客户端可以正常连接到集群中的任何节点。如果该节点失败,而集群的其余部分幸存下来,那么客户机应该注意到关闭的连接,并且应该能够重新连接到集群中幸存的一些成员。通常,将节点主机名或IP地址放入客户机应用程序中是不可取的:这带来了不灵活性,如果集群配置更改或集群中的节点数量更改,则需要对客户机应用程序进行编辑、重新编译和重新部署。相反,我们推荐一种更抽象的方法:这可以是一个动态DNS服务,它具有非常短的TTL配置,或者是一个简单的TCP负载均衡器,或者是使用pacemaker或类似技术实现的某种移动IP。通常,在集群中管理到节点的连接的这方面超出了RabbitMQ本身的范围,我们建议使用专门为解决这些问题而设计的其他技术。

含RAM节点的集群

RAM节点只在内存中保存它们的元数据。由于RAM节点不必像磁盘节点那样频繁地写入磁盘,所以它们可以执行得更好。但是,请注意,由于持久队列数据总是存储在磁盘上,因此性能改进只会影响资源管理(例如,添加/删除队列、交换器或vhost),而不会影响发布或消耗速度。
RAM节点是一个高级案例;在设置第一个集群时,不应该使用它们。您应该有足够的磁盘节点来处理冗余需求,然后在必要时添加额外的RAM节点。 只包含RAM节点的集群太不稳定;如果集群停止,您将无法再次启动它,并将丢失所有数据。RabbitMQ将在许多情况下阻止只创建ram节点的集群,但它不能完全阻止它。
为了简单起见,这里的示例显示一个具有一个磁盘和一个RAM节点的集群;这样的集群是一个糟糕的设计选择。

创建RAM节点

我们可以在节点第一次加入集群时将其声明为RAM节点。我们像之前一样使用rabbitmqctl join_cluster来做这件事,但是要传递“--ram”选项:

# on rabbit2
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit2 ...done.

rabbitmqctl join_cluster --ram rabbit@rabbit1
# => Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.

rabbitmqctl start_app
# => Starting node rabbit@rabbit2 ...done.
复制代码

在集群状态下,RAM节点如下图所示:

# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1]},{ram,[rabbit@rabbit2]}]},
# =>  {running_nodes,[rabbit@rabbit2,rabbit@rabbit1]}]
# => ...done.

# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit1]},{ram,[rabbit@rabbit2]}]},
# =>  {running_nodes,[rabbit@rabbit1,rabbit@rabbit2]}]
# => ...done.
复制代码

变更节点类型

我们可以将节点的类型从RAM更改为Disk,反之亦然。假设我们想要反转rabbit@rabbit2和rabbit@rabbit1的类型,将前者从RAM节点转换为磁盘节点,而后者从磁盘节点转换为RAM节点。为此,我们可以使用change_cluster_node_type命令。必须首先停止节点。

# on rabbit2
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit2 ...done.

rabbitmqctl change_cluster_node_type disc
# => Turning rabbit@rabbit2 into a disc node ...done.

rabbitmqctl start_app
# => Starting node rabbit@rabbit2 ...done.

# on rabbit1
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit1 ...done.

rabbitmqctl change_cluster_node_type ram
# => Turning rabbit@rabbit1 into a ram node ...done.

rabbitmqctl start_app
# => Starting node rabbit@rabbit1 ...done.
复制代码

上一篇:RabbitMQ系列(九)分布式RabbitMQ概述
下一篇:RabbitMQ系列(十一)分布式RabbitMQ(队列镜像)