集群指南
概述
本指南涵盖了与 RabbitMQ 集群相关的基本主题
- RabbitMQ 节点如何识别:节点名称
- 集群要求
- 哪些数据在集群节点之间复制,哪些数据不复制
- 集群对客户端意味着什么
- 集群是如何形成的
- 节点如何相互认证(以及与 CLI 工具的认证)
- 为什么使用奇数个节点很重要,并且**强烈建议不要使用两个节点的集群**
- 队列和流领导者副本放置策略
- 节点重启以及节点如何重新加入其集群
- 节点就绪探测以及它们如何影响滚动集群重启
- 如何移除集群节点
- 如何重置集群节点到原始(空白)状态
以及更多。 集群形成和对等发现是一个密切相关的指南,重点关注对等发现和集群形成自动化相关的主题。有关队列内容(消息)复制,请参阅仲裁队列指南。
VMware Tanzu RabbitMQ 提供了集群内压缩功能。
RabbitMQ 集群是逻辑上将一个或多个节点组合在一起,每个节点共享用户、虚拟主机、队列、交换机、绑定、运行时参数和其他分布式状态。
集群形成
集群形成的方式
RabbitMQ 集群可以通过多种方式形成
- 在配置文件中列出集群节点,以声明方式进行
- 使用基于 DNS 的发现,以声明方式进行
- 使用AWS(EC2)实例发现(通过插件),以声明方式进行
- 使用Kubernetes 发现(通过插件),以声明方式进行
- 使用基于 Consul 的发现(通过插件),以声明方式进行
- 使用基于 etcd 的发现(通过插件),以声明方式进行
- 使用
rabbitmqctl
手动进行
请参阅集群形成指南以获取详细信息。
集群的组成可以动态更改。所有 RabbitMQ 代理最初都运行在单个节点上。这些节点可以加入集群,然后再次变成单个代理。
节点名称(标识符)
RabbitMQ 节点由节点名称识别。节点名称由两部分组成,一个前缀(通常为 rabbit
)和主机名。例如,rabbit@node1.messaging.svc.local
是一个节点名称,其前缀为 rabbit
,主机名为 node1.messaging.svc.local
。
集群中的节点名称必须唯一。如果在一个给定主机上运行多个节点(这通常是开发和 QA 环境中的情况),则它们必须使用不同的前缀,例如 rabbit1@hostname
和 rabbit2@hostname
。
在集群中,节点使用节点名称相互识别和联系。这意味着每个节点名称的主机名部分必须能够解析。 CLI 工具也使用节点名称识别和寻址节点。
当节点启动时,它会检查是否已为其分配了节点名称。这是通过 RABBITMQ_NODENAME
环境变量完成的。如果未显式配置任何值,则节点会解析其主机名并在其前面加上 rabbit
来计算其节点名称。
如果系统对主机名使用完全限定域名 (FQDN),则必须将 RabbitMQ 节点和 CLI 工具配置为使用所谓的长节点名称。对于服务器节点,这是通过将 RABBITMQ_USE_LONGNAME
环境变量设置为 true
来完成的。
对于 CLI 工具,必须设置 RABBITMQ_USE_LONGNAME
或指定 --longnames
选项。
集群形成要求
主机名解析
RabbitMQ 节点使用**节点名称**(前缀和域名组合,可以是短名称或完全限定名称 (FQDN))相互寻址。
因此,每个集群成员**必须能够解析每个其他集群成员的主机名**、它自己的主机名,以及可能使用命令行工具(如 rabbitmqctl
)的机器的主机名。
节点将在节点启动时尽早执行主机名解析。在基于容器的环境中,重要的是在容器启动之前主机名解析已准备就绪。对于 Kubernetes 用户,这意味着将CoreDNS 的 DNS 缓存间隔设置为 5-10 秒范围内的值。
主机名解析可以使用任何标准的操作系统提供的方
- DNS 记录
- 本地主机文件(例如
/etc/hosts
)
在更严格的环境中,如果限制了 DNS 记录或主机文件修改,或者修改是不可能或不希望的,则可以配置 Erlang 虚拟机以使用备选主机名解析方法,例如备选 DNS 服务器、本地文件、非标准主机文件位置或方法混合。这些方法可以与标准的操作系统主机名解析方法协同工作。
要使用 FQDN,请参阅配置指南中的 RABBITMQ_USE_LONGNAME
。请参阅上面的节点名称。
端口访问
RabbitMQ 节点绑定到端口(打开服务器 TCP 套接字)以接受客户端和 CLI 工具连接。其他进程和工具(如 SELinux)可能会阻止 RabbitMQ 绑定到端口。发生这种情况时,节点将无法启动。
CLI 工具、客户端库和 RabbitMQ 节点也会打开连接(客户端 TCP 套接字)。防火墙可以阻止节点和 CLI 工具相互通信。以下端口与集群中的节点间通信最相关
- 4369:epmd,RabbitMQ 节点和 CLI 工具使用的辅助发现守护程序
- 6000 到 6500:由RabbitMQ Stream 复制使用
- 25672:用于节点间和 CLI 工具通信(Erlang 分布式服务器端口),并从动态范围分配(默认情况下限于单个端口,计算为 AMQP 端口 + 20000)。除非确实需要这些端口上的外部连接(例如,集群使用联合或在子网外部使用 CLI 工具),否则不应公开这些端口。请参阅网络指南以获取详细信息。
- 35672-35682:由 CLI 工具(Erlang 分布式客户端端口)用于与节点通信,并从动态范围分配(计算为服务器分布式端口 + 10000 到服务器分布式端口 + 10010)。
可以配置 RabbitMQ以使用不同的端口和特定的网络接口。请参阅RabbitMQ 网络指南以了解更多信息。
集群中的节点
复制什么?
所有用于 RabbitMQ 代理操作的数据/状态都将在所有节点之间复制。例外情况是消息队列,默认情况下它们驻留在一个节点上,尽管它们对所有节点都是可见且可访问的。要在集群中的节点之间复制队列,请使用支持复制的队列类型。本主题在仲裁队列指南中进行了介绍。
节点是平等的对等体
一些分布式系统具有领导者和跟随者节点。这通常不适用于 RabbitMQ。RabbitMQ 集群中的所有节点都是平等的对等体:RabbitMQ 核心没有特殊节点。当考虑仲裁队列和插件时,此主题变得更加细致入微,但对于大多数目的,应将所有集群节点视为平等的。
许多CLI 工具操作都可以在任何节点上执行。 HTTP API 客户端可以定位任何集群节点。
各个插件可以指定(选举)某些节点在一段时间内成为“特殊”节点。例如,联合链接位于特定的集群节点上。如果该节点发生故障,链接将在其他节点上重新启动。
在较旧(维护时间较长)的版本中,RabbitMQ 管理插件使用专用节点进行统计信息收集和聚合。
CLI 工具如何向节点(以及节点如何相互)进行身份验证:Erlang Cookie
RabbitMQ 节点和 CLI 工具(例如 rabbitmqctl
)使用 cookie 来确定它们是否允许相互通信。为了使两个节点能够通信,它们必须具有相同的共享密钥,称为 Erlang cookie。cookie 只是一个最多 255 个字符的字母数字字符字符串。它通常存储在本地文件中。该文件必须仅对所有者可访问(例如,具有 600
或类似的 UNIX 权限)。每个集群节点都必须具有相同的 cookie。
如果文件不存在,Erlang VM 会在 RabbitMQ 服务器启动时尝试使用随机生成的值创建一个。仅在开发环境中使用此类生成的 cookie 文件是合适的。由于每个节点都会独立生成自己的值,因此此策略在 集群环境 中并不实用。
Erlang cookie 生成应在集群部署阶段完成,理想情况下应使用自动化和编排工具。
在分布式部署中
Cookie 文件位置
Linux、MacOS、*BSD
在 UNIX 系统上,cookie 通常位于 /var/lib/rabbitmq/.erlang.cookie
(服务器使用)和 $HOME/.erlang.cookie
(CLI 工具使用)。请注意,由于 $HOME
的值因用户而异,因此需要为每个将使用 CLI 工具的用户放置 cookie 文件的副本。这适用于非特权用户和 root
。
RabbitMQ 节点将在启动时记录其有效用户的 home 目录位置。
社区 Docker 镜像和 Kubernetes
Docker 社区 RabbitMQ 镜像 使用 RABBITMQ_ERLANG_COOKIE
环境变量值来填充 cookie 文件。
使用此镜像的配置管理和容器编排工具必须确保集群中的每个 RabbitMQ 节点容器都使用相同的值。
在 Kubernetes 上下文中,必须在 StatefulSet 的 pod 模板规范中指定该值。例如,这可以在 RabbitMQ on Kubernetes 示例存储库 中看到。
Windows
在 Windows 上,cookie 位置取决于一些因素
HOMEDRIVE
和HOMEPATH
环境变量是否都已设置- Erlang 版本:早于 20.2(这些版本不再受任何 RabbitMQ 的维护版本系列 支持)或 20.2 及更高版本
Erlang 20.2 或更高版本
对于 Erlang 20.2 及更高版本,cookie 文件位置为:
%HOMEDRIVE%%HOMEPATH%\.erlang.cookie
(对于用户%USERNAME%
通常为C:\Users\%USERNAME%\.erlang.cookie
),如果HOMEDRIVE
和HOMEPATH
环境变量都已设置%USERPROFILE%\.erlang.cookie
(通常为C:\Users\%USERNAME%\.erlang.cookie
),如果HOMEDRIVE
和HOMEPATH
未都设置- 对于 RabbitMQ Windows 服务 -
%USERPROFILE%\.erlang.cookie
(通常为C:\WINDOWS\system32\config\systemprofile
)
如果使用 Windows 服务,则应将 cookie 从 C:\Windows\system32\config\systemprofile\.erlang.cookie
复制到运行 rabbitmqctl.bat
等命令的用户期望的位置。
使用 CLI 和运行时命令行参数覆盖
或者,可以将选项“-setcookie <value>
”添加到 RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS
环境变量值 以覆盖 RabbitMQ 节点使用的 cookie 值
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-setcookie cookie-value"
CLI 工具可以使用命令行标志获取 cookie 值
rabbitmq-diagnostics status --erlang-cookie "cookie-value"
两者都是安全性最低的选项,通常不建议使用。
故障排除
当节点启动时,它将记录其有效用户的 home 目录位置
node : rabbit@cdbf4de5f22d
home dir : /var/lib/rabbitmq
除非任何 服务器目录 被覆盖,否则该目录就是节点将在其中查找 cookie 文件的目录,如果该文件尚不存在,则节点将在第一次启动时创建该文件。
在上面的示例中,cookie 文件位置将为 /var/lib/rabbitmq/.erlang.cookie
。
身份验证失败
当 cookie 配置错误(例如,不相同)时,RabbitMQ 节点将记录诸如“来自不允许的节点的连接尝试”、“”,“无法自动集群”之类的错误。
例如,当 CLI 工具连接并尝试使用不匹配的密钥值进行身份验证时
2020-06-15 13:03:33 [error] <0.1187.0> ** Connection attempt from node 'rabbitmqcli-99391-rabbit@warp10' rejected. Invalid challenge reply. **
当 CLI 工具(例如 rabbitmqctl
)无法对 RabbitMQ 进行身份验证时,消息通常会显示
* 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 工具指南。
主机名解析
由于主机名解析是 节点间通信成功的前提条件,因此从 RabbitMQ 3.8.6
开始,CLI 工具提供了两个命令来帮助验证节点上的主机名解析是否按预期工作。这些命令并非旨在替代 dig
和其他专门的 DNS 工具,而是提供了一种方法,可以在考虑 Erlang 运行时主机名解析器功能 的情况下执行最基本的检查。
这些命令在 网络指南 中进行了介绍。
CLI 工具
从 3.8.6
版本 开始,rabbitmq-diagnostics
包含一个命令,该命令提供有关 CLI 工具使用的 Erlang cookie 文件的相关信息
rabbitmq-diagnostics erlang_cookie_sources
该命令将报告有效用户、用户 home 目录和 cookie 文件的预期位置
Cookie File
Effective user: antares
Effective home directory: /home/cli-user
Cookie file path: /home/cli-user/.erlang.cookie
Cookie file exists? true
Cookie file type: regular
Cookie file access: read
Cookie file size: 20
Cookie CLI Switch
--erlang-cookie value set? false
--erlang-cookie value length: 0
Env variable (Deprecated)
RABBITMQ_ERLANG_COOKIE value set? false
RABBITMQ_ERLANG_COOKIE value length: 0
节点计数和仲裁
由于一些功能(例如 仲裁队列、MQTT 中的客户端跟踪)需要集群成员之间达成共识,因此强烈建议使用奇数个集群节点:1、3、5、7 等。
强烈建议不要使用两个节点的集群,因为在连接丢失的情况下,集群节点无法识别多数并达成共识。例如,当两个节点失去连接时,MQTT 客户端连接将不被接受,仲裁队列将失去可用性,等等。
从共识的角度来看,四个或六个节点的集群将具有与三个和五个节点的集群相同的可用性特性。
有关此主题的更多详细信息,请参阅 仲裁队列指南。
集群和客户端
消息传递协议
假设所有集群成员都可用,消息传递(AMQP 0-9-1、AMQP 1.0、MQTT、STOMP)客户端可以连接到任何节点并执行任何操作。节点将透明地将操作路由到 仲裁队列领导者,以供客户端使用。
对于所有受支持的消息传递协议,客户端一次仅连接到一个节点。
如果节点发生故障,客户端应该能够重新连接到其他节点,恢复其拓扑并继续操作。出于这个原因,大多数客户端库都接受端点(主机名或 IP 地址)列表作为连接选项。该主机列表将在初始连接以及连接恢复期间使用(如果客户端支持)。请参阅各个客户端的文档指南以了解更多信息。
使用 仲裁队列 和 流,客户端只能对具有在线副本仲裁的队列执行操作。
流客户端
RabbitMQ 流协议客户端的行为与消息传递协议客户端不同:它们更了解集群拓扑。对于发布,它们可以连接到任何节点,并且该节点将所有相关操作转发到托管流的领导者副本的节点。
但是,流使用者应连接到托管目标流副本的节点之一。该协议包含拓扑发现操作,因此行为良好的客户端库将选择合适的节点之一。但是,当使用负载均衡器时,情况并非如此。
有关更多信息,请参阅 连接到流。
队列和流领导者副本放置
RabbitMQ 中的每个队列和流都有一个主副本(对于经典队列,它是唯一的副本)。该副本称为领导者。对队列和流的所有发布操作都首先通过领导者副本,然后复制到跟随者(辅助副本)。这是为了保证消息的 FIFO 顺序。
为了避免集群中某些节点托管大量队列领导者副本,从而处理大部分负载,应在集群节点之间合理地均匀分布队列领导者。
可以通过三种方式控制队列领导者分布
- 策略,通过设置
queue-leader-locator
- 配置文件,通过设置
queue_leader_locator
- 可选队列参数,通过设置
x-queue-leader-locator
(不建议使用)
有两种选项可用
client-local
,默认值,将始终选择客户端连接到的节点balanced
,它会考虑集群中每个节点上已运行的队列/领导者的数量;当集群中队列相对较少时,它会选择队列数量最少的节点;当队列很多(默认情况下超过 1000 个)时,它只会随机选择一个节点(计算确切数量在队列很多时可能会很慢,并且随机选择通常也一样好)
以下示例设置rabbitmq.conf
中的queue_leader_locator
设置,以确保队列的均衡分布。
queue_leader_locator = balanced
当同时使用客户端提供的队列参数和配置参数时,客户端提供的队列参数优先。
请注意,所有基于Raft的功能,即仲裁队列和流,都将此值用作建议。Raft领导者选举算法涉及一定程度的随机性,因此选定的推荐节点将在其上放置一个副本,但它并不总是领导者副本。
为了向后兼容,经典队列仍然支持queue-master-locator
(策略参数)、x-queue-master-locator
(队列参数)和queue_master_locator
(配置选项)。但是,这些选项已弃用,建议使用上面列出的选项。
这些选项允许不同的值:client-local
、random
和min-masters
。后两个现在在内部映射到balanced
。
集群和可观察性
客户端连接、通道和队列将在集群节点之间分布。操作员需要能够跨所有集群节点检查和监控这些资源。
RabbitMQ CLI工具(例如rabbitmq-diagnostics
和rabbitmqctl
)提供了检查资源和集群范围状态的命令。一些命令侧重于单个节点的状态(例如rabbitmq-diagnostics environment
和rabbitmq-diagnostics status
),其他命令检查集群范围的状态。后者的一些示例包括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命令将产生相同或语义上相同的结果。“节点本地”命令不会产生相同的结果,因为两个节点很少具有相同的状态:至少它们的节点名称将不同!
管理UI的工作原理类似:必须响应HTTP API请求的节点将分发到其他集群成员并聚合它们的响应。在一个启用了管理插件的多个节点的集群中,操作员可以使用任何节点访问管理UI。对于使用HTTP API收集有关集群状态的数据的监控工具也是如此。无需依次向每个集群节点发出请求。
节点故障处理
RabbitMQ代理可以容忍单个节点的故障。可以随意启动和停止节点,只要它们能够联系到关闭时已知的集群成员节点即可。
仲裁队列允许队列内容在多个集群节点之间复制,并具有并行复制以及可预测的领导者选举和数据安全行为,只要大多数副本在线即可。
非复制的经典队列也可以在集群中使用。它们在节点故障情况下的行为取决于队列持久性。
RabbitMQ集群有多种处理网络分区的方式,主要以一致性为导向。集群旨在跨LAN使用。不建议运行跨WAN的集群。Shovel或Federation插件是跨WAN连接代理的更好解决方案。请注意,Shovel和Federation不等同于集群。
指标和统计信息
每个节点都存储和聚合自己的指标和统计信息,并提供一个API供其他节点访问。一些统计信息是集群范围的,其他统计信息特定于各个节点。响应HTTP API请求的节点会联系其对等节点以检索其数据,然后生成聚合结果。
在较旧的(长期未维护的)版本中,RabbitMQ管理插件使用一个专用的节点进行统计信息收集和聚合。
使用rabbitmqctl
的集群记录
以下几个部分提供了在三台机器(rabbit1
、rabbit2
、rabbit3
)上手动设置和操作RabbitMQ集群的记录。建议在使用更自动化友好的集群形成选项之前先研究此示例。
我们假设用户已登录所有三台机器,RabbitMQ已安装在这些机器上,并且rabbitmq-server和rabbitmqctl脚本位于用户的PATH中。
此记录可以修改为在一台主机上运行,如下面更详细地解释。
启动独立节点
通过将现有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.
通过遵循上述步骤,我们可以在任何时间将新节点添加到集群中,同时集群正在运行。
重新启动集群节点
在Kubernetes上运行RabbitMQ的用户还必须查阅以下部分,其中解释了如何避免特定于Kubernetes的众所周知的集群重启死锁场景。
已加入集群的节点可以随时停止。它们也可能发生故障或被操作系统终止。
通常,如果大多数节点在节点停止后仍然在线,则不会影响集群的其余部分,尽管客户端连接分布、队列副本放置和集群的负载分布将发生变化。
从在线对等节点同步模式
重新启动的节点将在启动时从其对等节点同步模式和其他信息。在此过程完成之前,节点将无法完全启动并正常运行。
因此,了解节点在停止和重新启动时经历的过程非常重要。
重新启动后,节点将默认尝试联系该对等节点10次,响应超时时间为30秒。这意味着默认情况下,所有集群成员必须在5分钟内上线。
在按顺序部署和验证节点的环境中,例如具有OrderedReady
Pod管理策略的Kubernetes,除非遵循一些建议,否则重启可能会陷入死锁。
停止的节点将在重启后选择一个在线集群成员(只考虑磁盘节点)进行同步。重新启动后,节点将默认尝试联系该对等节点10次,响应超时时间为30秒。
如果对等节点在该时间间隔内可用,则节点将成功启动,从对等节点同步所需内容并继续运行。
如果对等节点不可用,则重新启动的节点将放弃并自愿停止。可以通过日志中最终导致节点启动失败的超时(timeout_waiting_for_tables
)警告消息来识别此类情况。
2020-07-27 21:10:51.361 [warning] <0.269.0> Error while waiting for Mnesia tables: {timeout_waiting_for_tables,[rabbit@node2,rabbit@node1],[rabbit_durable_queue]}
2020-07-27 21:10:51.361 [info] <0.269.0> Waiting for Mnesia tables for 30000 ms, 1 retries left
2020-07-27 21:11:21.362 [warning] <0.269.0> Error while waiting for Mnesia tables: {timeout_waiting_for_tables,[rabbit@node2,rabbit@node1],[rabbit_durable_queue]}
2020-07-27 21:11:21.362 [info] <0.269.0> Waiting for Mnesia tables for 30000 ms, 0 retries left
2020-07-27 21:15:51.380 [info] <0.269.0> Waiting for Mnesia tables for 30000 ms, 1 retries left
2020-07-27 21:16:21.381 [warning] <0.269.0> Error while waiting for Mnesia tables: {timeout_waiting_for_tables,[rabbit@node2,rabbit@node1],[rabbit_user,rabbit_user_permission, …]}
2020-07-27 21:16:21.381 [info] <0.269.0> Waiting for Mnesia tables for 30000 ms, 0 retries left
2020-07-27 21:16:51.393 [info] <0.44.0> Application mnesia exited with reason: stopped
2020-07-27 21:16:51.397 [error] <0.269.0> BOOT FAILED
2020-07-27 21:16:51.397 [error] <0.269.0> ===========
2020-07-27 21:16:51.397 [error] <0.269.0> Timeout contacting cluster nodes: [rabbit@node1].
当节点在关闭期间没有在线对等节点时,它将启动而无需尝试与任何已知对等节点同步。但是,它不会作为独立节点启动,并且对等节点将能够重新加入它。
当整个集群被关闭时,最后一个关闭的节点是唯一一个在关闭时没有运行中的对等节点的节点。该节点可以在不首先联系任何对等节点的情况下启动。由于节点会尝试联系已知的对等节点长达 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 分钟才能完成。
在升级期间,有时最后一个停止的节点必须是升级后第一个启动的节点。该节点将被指定执行集群范围的模式迁移,其他节点可以在重新加入时从该节点同步并应用。
节点重启、Kubernetes Pod 管理和健康检查(就绪探测)
在 Kubernetes 上运行 RabbitMQ 时,使用Parallel
pod 管理策略。
在某些环境中,节点重启由指定的健康检查控制。这些检查验证一个节点是否已启动,并且部署过程可以继续进行到下一个节点。如果检查未通过,则节点的部署被认为是不完整的,部署过程通常会等待一段时间并重试。这种环境的一个常见示例是 Kubernetes,其中操作员定义的就绪探测可以防止部署在使用OrderedReady
pod 管理策略时继续进行。使用Parallel
pod 管理策略有助于避免此问题。
鉴于上面描述的对等节点同步行为,此类健康检查可能会阻止集群范围内的重启及时完成。明确或隐含地假设已完全启动并重新加入其集群对等节点的节点的检查将失败并阻止进一步的节点部署。
大多数健康检查,即使是相对基本的检查,也隐含地假设节点已完成启动。它们不适用于正在等待从对等节点同步模式表的节点。
此类检查的一个非常常见的示例是
# will exit with an error for the nodes that are currently waiting for
# a peer to sync schema tables from
rabbitmq-diagnostics check_running
一个不期望节点完全启动并已同步模式表的健康检查是
# a very basic check that will succeed for the nodes that are currently waiting for
# a peer to sync schema from
rabbitmq-diagnostics ping
此基本检查将允许部署继续进行,并且节点最终相互重新加入,假设它们是兼容的。
重启之间主机名更改
如果节点的数据目录路径因此发生更改,则在节点名称或主机名更改后重新加入的节点可以作为空白节点启动。此类节点将无法重新加入集群。在节点脱机时,其对等节点可以重置或使用空白数据目录启动。在这种情况下,恢复的节点也将无法重新加入其对等节点,因为内部数据存储集群标识将不再匹配。
考虑以下场景
- 形成了一个由 3 个节点(A、B 和 C)组成的集群
- 节点 A 已关闭
- 节点 B 已重置
- 节点 A 已启动
- 节点 A 尝试重新加入 B,但 B 的集群标识已更改
- 节点 B 无法识别 A 为已知的集群成员,因为它已被重置
在这种情况下,节点 B 将拒绝来自 A 的集群尝试,并在日志中显示相应的错误消息
Node 'rabbit@node1.local' thinks it's clustered with node 'rabbit@node2.local', but 'rabbit@node2.local' disagrees
在这种情况下,可以再次重置 B,然后它将能够加入 A,或者可以重置 A,并且将成功加入 B。
集群节点重启示例
以下示例使用 CLI 工具关闭节点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.
在对等节点不可用时强制节点启动
在某些情况下,最后一个脱机的节点无法重新启动。可以使用forget_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@rabbit1
。
# 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 版本:可以混合使用 26.2.0
和 26.1.2
,但 25.3.2.8
和 26.2.0
可能会在节点间通信协议中引入重大更改。虽然此类重大更改很少见,但它们是可能的。
Erlang/OTP 版本的补丁版本之间的不兼容性非常罕见。
从客户端连接到集群
客户端可以正常连接到集群中的任何节点。如果该节点发生故障,并且集群的其余部分仍然存活,则客户端应该注意到已关闭的连接,并且应该能够重新连接到集群中的一些存活成员。
许多客户端支持在连接时按顺序尝试的主机名列表。
通常不建议在客户端应用程序中硬编码 IP 地址:这会降低灵活性,并且如果集群的配置发生更改或集群中的节点数量发生更改,则需要编辑、重新编译和重新部署客户端应用程序。
相反,请考虑更抽象的方法:这可以是具有非常短 TTL 配置的动态 DNS 服务,或一个简单的 TCP 负载均衡器,或它们的组合。
通常,管理与集群内节点连接的这一方面超出了本指南的范围,我们建议使用专门设计用于解决这些问题的其他技术。