集群指南
本指南主要面向 RabbitMQ 4.x 中的新元数据存储 Khepri。
概述
本指南涵盖了与 RabbitMQ 集群相关的基本主题
- RabbitMQ 节点如何识别:节点名称
- 集群的要求
- 哪些数据会在集群节点之间复制,哪些不会
- 集群对客户端意味着什么
- 集群如何形成
- 节点如何相互认证(以及与 CLI 工具认证)
- 为什么使用奇数个节点很重要,并且强烈不建议使用双节点集群
- 队列和流领导者副本放置策略
- 节点重启以及节点如何重新加入集群
- 节点就绪探针以及它们如何影响滚动集群重启
- 如何移除集群节点
- 如何将集群节点重置到原始(空白)状态
等等。集群形成和对等发现是一份密切相关的指南,侧重于对等发现和集群形成自动化相关主题。有关队列和流内容(消息)复制,请参阅仲裁队列和流指南。
VMware Tanzu RabbitMQ 提供了集群内压缩功能。
什么是集群?
RabbitMQ 集群是包含一个或多个(三个、五个、七个或更多)节点的逻辑分组,每个节点共享用户、虚拟主机、队列、流、交换器、绑定、运行时参数和其他分布式状态。
要形成集群,节点必须以特定方式配置并满足一系列要求,例如开放端口访问。
集群形成后,集群中的所有节点都会感知到其他集群成员。
客户端应用程序可以感知也可以不感知存在多个集群节点的事实,并连接到其中任何一个,或者根据使用的协议,连接到其中的一个子集。例如,RabbitMQ 流协议客户端可以同时连接到多个节点。本指南稍后会更详细地介绍这一点。
集群形成
形成集群的方式
RabbitMQ 集群可以通过多种方式形成
- 通过在配置文件中列出集群节点来声明式配置
- 通过基于 DNS 的发现来声明式配置
- 通过AWS (EC2) 实例发现(通过插件)来声明式配置
- 通过Kubernetes 发现(通过插件)来声明式配置
- 通过Consul-based 发现(通过插件)来声明式配置
- 通过etcd-based 发现(通过插件)来声明式配置
- 使用
rabbitmqctl join_cluster手动配置
这些机制在集群形成指南中得到了更详细的介绍。
集群的组成可以动态更改。所有 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 VM 可以配置为使用替代的主机名解析方法,例如替代 DNS 服务器、本地文件、非标准主机文件位置或多种方法的组合。这些方法可以与标准的操作系统主机名解析方法结合使用。
要使用 FQDN,请参阅配置指南中的 RABBITMQ_USE_LONGNAME。请参阅节点名称。
集群和复制必须打开的端口
RabbitMQ 节点绑定到端口(打开服务器 TCP 套接字)以接受客户端和 CLI 工具连接。SELinux 等其他进程和工具可能会阻止 RabbitMQ 绑定到端口。发生这种情况时,节点将无法启动。
CLI 工具、客户端库和 RabbitMQ 节点也打开连接(客户端 TCP 套接字)。防火墙可能会阻止节点和 CLI 工具相互通信。以下端口与集群中的节点间通信最相关
- 4369:epmd,RabbitMQ 节点和 CLI 工具使用的辅助发现守护进程
- 6000 至 6500:用于RabbitMQ 流复制
- 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 个字符的字母数字字符串。它通常存储在一个本地文件中。文件只能由所有者访问(例如,UNIX 权限为 600 或类似)。每个集群节点都必须具有相同的 cookie。
如果文件不存在,Erlang VM 将在 RabbitMQ 服务器启动时尝试创建一个具有随机生成值的 cookie。使用这种生成的 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 模板规范中指定。例如,可以在RabbitMQ on Kubernetes 示例存储库中看到这一点。
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 文件位置将是 /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. **
当 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
节点计数和仲裁
由于许多功能(例如仲裁队列、MQTT 中的客户端跟踪)需要集群成员之间的共识,因此强烈建议使用奇数个集群节点:1、3、5、7 等。
强烈反对使用双节点集群,因为在连接丢失的情况下,集群节点无法识别多数并形成共识。例如,当两个节点丢失连接时,MQTT 客户端连接将不会被接受,仲裁队列将失去可用性,等等。
从共识的角度来看,四节点或六节点集群将具有与三节点和五节点集群相同的可用性特征。
《仲裁队列指南》更详细地介绍了这一主题。
集群和客户端
消息传递协议
假设所有集群成员都可用,消息传递(AMQP 0-9-1、AMQP 1.0、MQTT、STOMP)客户端可以连接到任何节点并执行任何操作。节点将透明地将操作路由到仲裁队列领导者。
对于所有支持的消息传递协议,客户端一次只能连接到一个节点。
在节点发生故障的情况下,客户端应能够重新连接到另一个节点,恢复其拓扑并继续操作。因此,大多数客户端库接受端点列表(主机名或 IP 地址)作为连接选项。主机列表将在初始连接和连接恢复时使用(如果客户端支持)。有关更多信息,请参阅各个客户端的文档指南。
对于仲裁队列和流,客户端只能对具有在线仲裁副本的队列执行操作。
流客户端
RabbitMQ 流协议客户端的**行为与消息传递协议客户端不同**:它们更了解集群拓扑。对于发布,它们可以连接到任何节点,该节点会将所有相关操作转发到托管流领导者副本的节点。
但是,流使用者应连接到托管目标流副本的节点之一。协议包含拓扑发现操作,因此行为良好的客户端库将选择合适的节点之一。但是,在使用负载均衡器时则不会出现这种情况。
有关更多信息,请参阅流客户端连接指南。
队列和流领导者副本放置
RabbitMQ 中的每个队列和流都有一个主副本(对于经典队列,这是唯一的副本)。该副本称为领导者。所有对队列和流的发布操作都首先通过领导者副本,然后复制到跟随者(次要副本)。这是保证消息 FIFO 顺序所必需的。
为了避免集群中的某些节点托管大部分队列领导者副本从而处理大部分负载,队列领导者应在集群节点之间合理均匀分布。
队列领导者分布可以通过三种方式控制
有两种可用选项
client-local,默认值,将始终选择客户端连接到的节点balanced,它会考虑集群中已运行的队列/领导者的数量;当集群中的队列相对较少时,它会选择队列数量最少的节点;当队列很多时(默认超过 1000 个),它只会随机选择一个节点(计算确切数量可能很慢,因为有许多队列,随机选择通常一样好)。
通常,如果声明队列的连接在节点之间均匀分布,client-local 策略是一个不错的选择。在这种情况下,尽管队列/领导者本地放置(在连接处),但在集群中它们仍然可以很好地平衡。否则,请首选 balanced 策略。balanced 策略的缺点是,当使用这些队列且选择了其他节点时,声明队列的连接的性能可能不是最佳的。例如,对于短暂的队列,client-local 可能是更好的选择。独占队列始终在本地声明。
以下示例在 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)。键入节点名称时,区分大小写,并且这些字符串必须完全匹配。
创建不带对等发现的集群
从 RabbitMQ 4.1.0 开始,节点不再需要停止和重置才能加入另一个节点。
rabbitmqctl join_cluster 执行必要的准备工作。
考虑使用对等发现机制之一,而不是使用 CLI 工具手动形成集群。
为了将我们的三个节点连接成一个集群,我们将其中两个节点(例如 rabbit@rabbit2 和 rabbit@rabbit3)告知加入第三个节点(例如 rabbit@rabbit1)的集群。
我们首先将 rabbit@rabbit2 加入 rabbit@rabbit1 的集群。
# on rabbit2
## Note: the original 'rabbitmqctl stop_app/reset/start_app' steps
## are no longer necessary starting with RabbitMQ 4.1, with both Khepri and Mnesia.
rabbitmqctl join_cluster rabbit@rabbit1
# => Clustering node rabbit@rabbit2 with [rabbit@rabbit1] ...done.
通过在任一节点上运行 cluster_status 命令,我们可以看到这两个节点已加入集群。
# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# ...
# Running Nodes
# rabbit@rabbit1
# rabbit@rabbit2
# ...
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# ...
现在我们将 rabbit@rabbit3 加入同一个集群。步骤与上述相同,只是这次我们将加入 rabbit2 以证明选择加入的节点无关紧要,只要提供一个在线节点即可,该节点将加入指定节点所属的集群。
# on rabbit3
## Note: the original 'rabbitmqctl stop_app/reset/start_app' steps
## are no longer necessary with RabbitMQ 4.2 and Khepri.
rabbitmqctl join_cluster rabbit@rabbit2
# => Clustering node rabbit@rabbit3 with rabbit@rabbit2 ...done.
通过在任一节点上运行 cluster_status 命令,我们可以看到这三个节点已加入集群。
# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# => rabbit@rabbit3
# ...
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# => rabbit@rabbit3
# ...
# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# => rabbit@rabbit3
# ...
通过遵循以上步骤,我们可以在集群运行时随时添加新节点。
重启集群节点
在 Kubernetes 上运行 RabbitMQ 的用户还必须参考以下部分,其中解释了如何避免 Kubernetes 特有的、众所周知的集群重启死锁场景。
已加入集群的节点可以随时停止。它们也可能发生故障或被操作系统终止。
通常,如果节点停止后大多数节点仍然在线,这不会影响集群的其余部分,尽管客户端连接分布、队列副本放置和集群的负载分布会发生变化。
从在线对等节点同步模式
重启的节点将在启动时从其对等节点同步模式和其他信息。在此过程完成之前,节点将无法完全启动和运行。
因此,了解节点在停止和重启时所经历的过程非常重要。
重启时,节点将默认尝试联系对等节点 10 次,响应超时时间为 30 秒。这意味着,默认情况下,所有集群成员必须在 5 分钟内在线。
在按顺序部署和验证节点的环境中,例如使用 OrderedReady pod 管理策略的 Kubernetes,除非遵循一系列建议,否则重启可能会陷入死锁。
停止节点会选择一个在线的集群成员(仅考虑磁盘节点)在重启后进行同步。重启后,节点将默认尝试联系该对等节点 10 次,响应超时时间为 30 秒。
如果对等节点在此时间间隔内可用,节点将成功启动,同步其所需数据,然后继续。
如果对等节点在 5 分钟内不可用,重启的节点将放弃并自愿停止。此类情况可以通过日志中的超时(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 ...
# ...
# => Running Nodes
# => Running Nodes
# => rabbit@rabbit2
# => rabbit@rabbit3
# ...
# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# ...
# => Running Nodes
# => Running Nodes
# => rabbit@rabbit2
# => rabbit@rabbit3
# ...
# on rabbit3
rabbitmqctl stop
# => Stopping and halting node rabbit@rabbit3 ...done.
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# ...
# => Running Nodes
# => rabbit@rabbit2
# ...
在下面的示例中,节点被重新启动,并在过程中检查集群状态。
# on rabbit1
rabbitmq-server -detached
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# ...
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# ...
# on rabbit3
rabbitmq-server -detached
# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# => rabbit@rabbit3
# ...
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# => rabbit@rabbit3
# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# => rabbit@rabbit3
如何从集群中移除节点
有时需要从集群中移除节点。
对于以下最常见的情况,操作顺序会略有不同
- 节点在线且可达
- 节点离线且无法恢复
此外,如果集群对等发现机制支持节点健康检查和强制移除不在发现后端已知的节点。
此功能是选择加入的(默认禁用)。
继续本指南中使用的三节点集群示例,让我们演示如何从集群中移除 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 ...
# => Disk Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# =>
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => Disk Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
# =>
# => Running Nodes
# => rabbit@rabbit1
# => rabbit@rabbit2
现在可以对节点 rabbit@rabbit3 进行退役,以便重置并作为独立节点启动。
# on rabbit3
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
# => Starting node rabbit@rabbit3 ...
rabbitmqctl cluster_status -n rabbit@rabbit3
# => Cluster status of node rabbit@rabbit3 ...
# => Disk Nodes
# => rabbit@rabbit3
# =>
# => Running Nodes
# => rabbit@rabbit3
节点可以远程移除,即从另一台主机移除,只要该主机上的 CLI 工具可以连接并认证到目标节点。
这可能很有用,例如,当需要处理无法访问的主机时。
在本例的其余部分,rabbit@rabbit1 将从其剩余的两个节点集群(与 rabbit@rabbit2)中移除。
# 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 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 节点集群可能很有用。这通常对于在单台主机上试验集群形成和多个节点很有用。
本指南历来涵盖了不带容器的单机集群形成。
要测试客户端库和应用程序与多个节点交互,请考虑使用社区 OCI 镜像、多个容器和容器端口转发。
要在同一台计算机上运行多个 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 join_cluster rabbit@`hostname -s`
将设置一个双节点集群,两个节点都是磁盘节点。请注意,如果节点监听除了 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 版本补丁版本之间的不兼容性非常罕见。