集群指南
概述
本指南涵盖与 RabbitMQ 集群相关的基本主题
- RabbitMQ 节点如何识别:节点名称
- 集群要求
- 哪些数据在集群节点之间复制,哪些不复制
- 集群对客户端意味着什么
- 集群如何形成
- 节点如何相互验证身份(以及与 CLI 工具)
- 为什么使用奇数个节点很重要,并且强烈建议不要使用双节点集群
- 队列和流领导者副本放置策略
- 节点重启以及节点如何重新加入其集群
- 节点就绪性探测以及它们如何影响滚动集群重启
- 如何移除集群节点
- 如何重置集群节点到初始(空白)状态
等等。集群形成和对等发现是一个密切相关的指南,重点介绍对等发现和集群形成自动化相关主题。有关队列内容(消息)复制,请参阅Quorum 队列指南。
VMware Tanzu RabbitMQ 提供了一个 集群内压缩功能。
什么是集群?
RabbitMQ 集群是一个或多个(三个、五个、七个或更多)节点的逻辑分组,每个节点共享用户、虚拟主机、队列、流、交换机、绑定、运行时参数和其他分布式状态。
要形成集群,必须以某种方式配置节点,并满足许多要求,例如开放端口访问。
集群形成后,集群中的所有节点都知道其他集群成员。
客户端应用程序可以知道或不知道存在多个集群节点,并连接到任何一个节点,或者,取决于使用的协议,连接到其中的一部分节点。例如,RabbitMQ Stream 协议客户端可以一次连接到多个节点。这将在本指南的后面更详细地介绍。
集群形成
形成集群的方式
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 记录或 hosts 文件修改受到限制、不可能或不希望进行修改,Erlang VM 可以配置为使用替代的主机名解析方法,例如替代的 DNS 服务器、本地文件、非标准 hosts 文件位置或方法的组合。这些方法可以与标准操作系统主机名解析方法协同工作。
要使用 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 代理操作所需的所有数据/状态都在所有节点之间复制。消息队列是此规则的例外,默认情况下消息队列驻留在一个节点上,但可以从所有节点查看和访问。要在集群中的节点之间复制队列,请使用支持复制的队列类型。本主题在Quorum 队列指南中介绍。
节点是对等节点
一些分布式系统具有领导者节点和跟随者节点。这通常不适用于 RabbitMQ。RabbitMQ 集群中的所有节点都是对等节点:RabbitMQ 核心中没有特殊节点。当考虑quorum 队列和插件时,此主题变得更加细致,但对于大多数意图和目的而言,所有集群节点都应被视为对等节点。
许多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 节点将在启动早期记录其有效用户的主目录位置。
社区 Docker 镜像和 Kubernetes
Docker 社区 RabbitMQ 镜像使用 RABBITMQ_ERLANG_COOKIE
环境变量值来填充 cookie 文件。
使用此镜像的配置管理和容器编排工具必须确保集群中的每个 RabbitMQ 节点容器都使用相同的值。
在 Kubernetes 的上下文中,必须在有状态集的 pod 模板规范中指定该值。例如,这可以在Kubernetes 上的 RabbitMQ 示例存储库中看到。
Windows
在 Windows 上,cookie 位置取决于几个因素
- 是否同时设置了
HOMEDRIVE
和HOMEPATH
环境变量 - Erlang 版本:20.2 之前(这些版本不再受任何 维护的 RabbitMQ 发布系列支持)或 20.2 及更高版本
Erlang 20.2 或更高版本
对于从 20.2 开始的 Erlang 版本,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"
两者都是最不安全的选项,通常不推荐使用。
故障排除
当节点启动时,它将记录其有效用户的主目录位置
node : rabbit@cdbf4de5f22d
home dir : /var/lib/rabbitmq
除非任何服务器目录被覆盖,否则这是将在其中查找 cookie 文件的目录,并且如果 cookie 文件尚不存在,则在首次启动时由节点创建。
在上面的示例中,cookie 文件位置将是 /var/lib/rabbitmq/.erlang.cookie
。
身份验证失败
当 cookie 配置错误时(例如,不相同),RabbitMQ 节点将记录诸如“Connection attempt from disallowed node”、“”、“Could not auto-cluster”之类的错误。
例如,当 CLI 工具连接并尝试使用不匹配的密钥值进行身份验证时
2020-06-15 13:03:33 [error] <0.1187.0> ** Connection attempt from node 'rabbitmqcli-99391-rabbit@warp10' rejected. Invalid challenge reply. **
当诸如 rabbitmqctl
之类的 CLI 工具无法通过 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
该命令将报告有效用户、用户主目录和 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
节点计数和仲裁
由于几个功能(例如quorum 队列、MQTT 中的客户端跟踪)需要在集群成员之间达成共识,因此强烈建议使用奇数个集群节点:1、3、5、7 等。
强烈建议不要使用双节点集群,因为在连接丢失的情况下,集群节点不可能识别多数并达成共识。例如,当两个节点失去连接时,将不接受 MQTT 客户端连接,quorum 队列将失去其可用性,等等。
从共识的角度来看,四个或六个节点集群与三个和五个节点集群具有相同的可用性特征。
Quorum 队列指南更详细地介绍了本主题。
集群和客户端
消息传递协议
假设所有集群成员都可用,则消息传递(AMQP 0-9-1、AMQP 1.0、MQTT、STOMP)客户端可以连接到任何节点并执行任何操作。节点会将操作透明地路由到quorum 队列领导者客户端。
对于所有支持的消息传递协议,客户端一次只能连接到一个节点。
如果节点发生故障,客户端应能够重新连接到不同的节点,恢复其拓扑并继续操作。因此,大多数客户端库接受端点列表(主机名或 IP 地址)作为连接选项。如果客户端支持,则主机列表将在初始连接以及连接恢复期间使用。请参阅各个客户端的文档指南以了解更多信息。
对于quorum 队列和流,客户端将只能对具有法定数量的在线副本的队列执行操作。
流客户端
RabbitMQ Stream 协议客户端的行为与消息传递协议客户端不同:它们更了解集群拓扑。对于发布,它们可以连接到任何节点,并且该节点会将所有相关操作转发到托管流的领导者副本的节点。
但是,流消费者应连接到托管目标流副本的节点之一。该协议包括拓扑发现操作,因此行为良好的客户端库将选择合适的节点之一。但是,当使用负载均衡器时,情况并非如此。
请参阅连接到流以了解更多信息。
队列和流领导者副本放置
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 的功能,即 quorum 队列和流,都将此值用作建议。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 代理可以容忍单个节点的故障。节点可以随意启动和停止,只要它们可以联系到关闭时已知的集群成员节点。
Quorum 队列允许队列内容跨多个集群节点复制,具有并行复制和可预测的领导者选举和数据安全行为,只要大多数副本在线。
非复制的经典队列也可以在集群中使用。它们在节点故障情况下的行为取决于队列持久性。
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
正如 cluster_status 命令所确认的那样,这将在每个节点上创建三个独立的 RabbitMQ Broker
# 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 Broker 的节点名称为 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 特有的众所周知的集群重启死锁场景。
已加入集群的节点可以随时停止。它们也可能发生故障或被操作系统终止。
一般来说,如果节点停止后,大多数节点仍然在线,则不会影响集群的其余部分,尽管客户端连接分布、队列副本放置和集群的负载分布会发生变化。
从在线对等节点同步 Schema
重启的节点将在启动时从其对等节点同步 schema 和其他信息。在此过程完成之前,该节点 不会完全启动和运行。
因此,了解节点在停止和重启时所经历的过程非常重要。
重启后,节点默认会尝试联系该对等节点 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 分钟才能完成的全集群重新部署场景。
在 升级 期间,有时最后一个停止的节点必须是升级后第一个启动的节点。该节点将被指定执行全集群 schema 迁移,其他节点可以在重新加入时从中同步和应用。
节点重启、Kubernetes Pod 管理和健康检查(就绪探针)
在 Kubernetes 上运行 RabbitMQ 时,请使用 Parallel
Pod 管理策略。
在某些环境中,节点重启由指定的健康检查控制。这些检查验证一个节点是否已启动,并且部署过程可以继续到下一个节点。如果检查未通过,则认为节点的部署不完整,并且部署过程通常会等待并重试一段时间。Kubernetes 是这种环境的一个流行示例,在 Kubernetes 中,当使用 OrderedReady
Pod 管理策略 时,操作员定义的就绪探针可以阻止部署继续进行。使用 Parallel
Pod 管理策略有助于避免此问题。
鉴于上面描述的对等节点同步行为,这样的健康检查可能会阻止全集群重启及时完成。显式或隐式假定已完全启动并已重新加入其集群对等节点的检查将失败并阻止进一步的节点部署。
大多数健康检查,即使是相对基本的检查,也隐含地假定节点已完成启动。它们不适用于等待从对等节点同步 schema 表的节点。
一个非常常见的此类检查示例是
# will exit with an error for the nodes that are currently waiting for
# a peer to sync schema tables from
rabbitmq-diagnostics check_running
一个不期望节点完全启动并同步 schema 表的健康检查是
# 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.
如何从集群中删除节点
有时需要从集群中删除节点。
对于以下最常见的场景,操作顺序会略有不同
- 节点在线且可访问
- 节点离线且无法恢复
此外,如果集群对等节点发现机制支持节点健康检查和强制删除未被发现后端识别的节点。
该功能是可选的(默认情况下已停用)。
继续本指南中使用的三节点集群示例,让我们演示如何从集群中删除 rabbit@rabbit3
,使其恢复独立运行。
删除可访问的节点
从集群中删除节点的第一步是停止它
# on rabbit3
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit3 ...done.
然后在另一个节点上使用 rabbitmqctl forget_cluster_node
,并将要删除的节点指定为 第一个位置参数
# on rabbit2
rabbitmqctl forget_cluster_node rabbit@rabbit3
# => Removing node rabbit@rabbit3 from cluster ...
运行
rabbitmq-diagnostics 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.
现在可以解除 rabbit@rabbit3
的配置,以便重置并作为独立节点启动
# on rabbit3
rabbitmqctl reset
rabbitmqctl start_app
# => Starting node rabbit@rabbit3 ...
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
# => ...done.
节点可以远程删除,即从不同的主机删除,只要该主机上的 CLI 工具可以连接并验证到目标节点即可。
例如,当必须处理无法访问的主机时,这可能很有用。
在本示例的其余部分中,将从其剩余的两个节点集群(包含 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.
删除停止的节点及其恢复
从集群中删除的节点在通过 rabbitmqctl stop_app
停止后,必须重置或解除配置。如果在未重置的情况下启动,则它将无法重新加入其原始集群。
此时,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 Broker。如果我们想重新初始化 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
从集群中删除它
# Tell rabbit@rabbit1 to permanently remove rabbit@rabbit2
rabbitmqctl forget_cluster_node -n rabbit@rabbit1 rabbit@rabbit2
# => Removing node rabbit@rabbit1 from cluster ...
# => ...done.
仲裁队列和流副本会发生什么?
当使用 CLI 工具从集群中删除节点时,节点上的所有仲裁队列和流副本都将被删除,即使这意味着队列和流将暂时具有偶数(例如,两个)副本。
节点删除是显式(手动)或可选的
除了 rabbitmqctl forget_cluster_node
和某些 对等节点发现 插件自动清理未知节点之外,在任何情况下,RabbitMQ 节点都不会永久地从集群中删除其对等节点。
如何重置节点
重置节点将删除其所有数据、集群成员信息、配置的运行时参数、用户、虚拟主机和任何其他节点数据。它还会更改其内部标识。
有时可能需要重置节点(具体含义见下文),然后再将其作为新节点重新加入集群。
一般来说,有两种可能的场景:节点正在运行时,以及节点无法启动或由于任何原因不对 CLI 工具命令做出响应时。
重置正在运行且响应迅速的节点
要重置正在运行且响应迅速的节点,首先使用 rabbitmqctl stop_app
停止其上的 RabbitMQ,然后使用 rabbitmqctl reset
重置它
# on rabbit1
rabbitmqctl stop_app
# => Stopping node rabbit@rabbit1 ...done.
rabbitmqctl reset
# => Resetting node rabbit@rabbit1 ...done.
如果重置节点在线且其集群对等节点可访问,则该节点将首先尝试从其集群中永久删除自身。
重置无响应的节点
对于无响应的节点,必须首先使用任何必要的方式将其停止。对于无法启动的节点,情况已经是这样。然后覆盖节点的数据目录位置或删除现有数据存储。这将使节点作为空白节点启动。必须指示它重新加入其原始集群(如果有)。
重置节点以将其作为全新节点重新添加到其原始集群
已从集群中删除的重置节点可以作为全新节点重新添加到其原始集群。
在这种情况下,它将同步所有虚拟主机、用户、权限和拓扑(队列、交换机、绑定)、运行时参数和策略。
为了将 仲裁队列 和 流 内容复制到新的[重新]添加的节点,必须使用 rabbitmq-queues grow
将该节点添加到放置副本的节点列表中。
重置节点上未复制的队列内容将丢失。
在对等节点不可用的情况下强制节点启动
在某些情况下,最后一个离线的节点无法重新启动。可以使用 forget_cluster_node
rabbitmqctl 命令将其从集群中删除。
或者,可以使用 force_boot
rabbitmqctl 命令在节点上使其启动,而无需尝试与任何对等节点同步(就像它们是最后一个关闭的一样)。这通常仅在最后一个关闭的节点或一组节点永远不会重新上线时才必要。
升级集群
您可以在升级指南中找到升级集群的说明。
单机上的集群
在某些情况下,在单台机器上运行 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
作为 Broker 节点名称,可以实现类似的效果。此解决方案的影响是集群将无法工作,因为所选主机名无法从远程主机解析为可路由的地址。从远程主机调用 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 负载均衡器,或者它们的组合。
总的来说,管理与集群内节点的连接的这一方面超出了本指南的范围,我们建议使用专门设计用于解决这些问题的其他技术。