跳至主内容
版本:4.3

集群指南

重要

本指南现在主要针对 RabbitMQ 4.x 中的新元数据存储:Khepri

概述

本指南涵盖了与 RabbitMQ 集群相关的基本主题

等等。集群形成与节点发现是一篇密切相关的指南,重点介绍节点发现和集群形成自动化相关的主题。关于队列和流内容(消息)复制,请参阅仲裁队列 (Quorum Queues)流 (Streams) 指南。

VMware Tanzu RabbitMQ 提供集群内压缩功能。

什么是集群?

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

要形成集群,节点必须以特定方式配置,并满足一些要求,例如开放端口访问。

集群形成后,集群中的所有节点都会感知到其他集群成员。

客户端应用程序可以感知或不感知存在多个集群节点,并连接到其中任何一个节点,或者根据使用的协议连接到其中的一部分。例如,RabbitMQ 流协议客户端可以同时连接到多个节点。本指南后续部分将对此进行更详细的介绍。

集群形成

集群形成方式

RabbitMQ 集群可以通过多种方式形成

这些机制在集群形成指南中有更详细的介绍。

集群的组成可以动态改变。所有 RabbitMQ 代理最初都运行在单个节点上。这些节点可以加入集群,随后也可以变回独立的代理。

节点名称(标识符)

RabbitMQ 节点由节点名称标识。节点名称由两部分组成:前缀(通常为 rabbit)和主机名。例如,rabbit@node1.messaging.svc.local 是一个节点名称,其前缀为 rabbit,主机名为 node1.messaging.svc.local

集群中的节点名称必须是唯一的。如果给定主机上运行多个节点(这在开发和 QA 环境中通常如此),它们必须使用不同的前缀,例如 rabbit1@hostnamerabbit2@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 记录
  • 本地 hosts 文件(例如 /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 流复制
  • 25672:用于节点间和 CLI 工具通信(Erlang 分布服务器端口),并从动态范围分配(默认限制为单个端口,计算为 AMQP 端口 + 20000)。除非确实需要这些端口上的外部连接(例如,集群使用 联邦 或在子网之外的计算机上使用 CLI 工具),否则不应公开这些端口。有关详细信息,请参阅 网络指南
  • 35672-35682:由 CLI 工具(Erlang 分布式客户端端口)用于与节点通信,并从动态范围分配(计算公式为服务器分布端口 + 10000 到服务器分布端口 + 10010)。

可以通过配置 RabbitMQ 来使用不同的端口和特定的网络接口。请参阅 RabbitMQ 网络指南以了解更多信息。

集群中的节点

哪些数据会被复制?

RabbitMQ 代理运行所需的所有数据/状态都会在所有节点间复制。消息队列是一个例外,默认情况下它们驻留在一个节点上,尽管它们对所有节点可见且可访问。要在集群中的节点间复制队列,请使用支持复制的队列类型。此主题包含在仲裁队列指南中。

节点是平等的对等体

某些分布式系统有领导者节点和跟随者节点。这通常不适用于 RabbitMQ。RabbitMQ 集群中的所有节点都是平等的对等体:RabbitMQ 核心中没有特殊节点。当考虑到仲裁队列和插件时,这个话题会变得更加细微,但对于大多数意图和目的而言,所有集群节点都应被视为平等的。

许多 CLI 工具操作可以在任何节点上执行。HTTP API 客户端可以针对任何集群节点。

个别插件可能会指定(选举)某些节点在一段时间内变得“特殊”。例如,联合链接 (federation links) 会放置在特定的集群节点上。如果该节点发生故障,链接将在另一个节点上重启。

在较旧的(长期维护的)版本中,RabbitMQ 管理插件使用专用节点进行统计数据的收集和聚合。

RabbitMQ 节点和 CLI 工具(例如 rabbitmqctl)使用 cookie 来确定它们是否被允许相互通信。为了使两个节点能够通信,它们必须拥有相同的共享密钥,称为 Erlang cookie。Cookie 只是一个最大长度为 255 个字符的字母数字字符串。它通常存储在本地文件中。该文件必须仅对所有者可访问(例如具有 600 或类似的 UNIX 权限)。每个集群节点必须拥有相同的 cookie。

如果文件不存在,当 RabbitMQ 服务器启动时,Erlang VM 会尝试创建一个具有随机生成值的 cookie。使用这种生成的 cookie 文件仅适用于开发环境。由于每个节点都会独立生成自己的值,因此这种策略在集群环境中并不真正可行。

Erlang 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 的上下文中,该值必须在有状态集 (StatefulSet) 的 Pod 模板规范中指定。例如,这可以在 RabbitMQ on Kubernetes 示例仓库中看到。

Windows

在 Windows 上,cookie 的位置取决于几个因素:

  • 是否同时设置了 HOMEDRIVEHOMEPATH 环境变量
  • Erlang 版本:早于 20.2(这些不再受任何受维护的 RabbitMQ 发布系列支持)或 20.2 及更高版本
Erlang 20.2 或更高版本

对于 20.2 及更高版本的 Erlang,cookie 文件的位置是:

  • 如果同时设置了 HOMEDRIVEHOMEPATH 环境变量,则为 %HOMEDRIVE%%HOMEPATH%\.erlang.cookie(对于用户 %USERNAME%,通常为 C:\Users\%USERNAME%\.erlang.cookie
  • 如果没有同时设置 HOMEDRIVEHOMEPATH,则为 %USERPROFILE%\.erlang.cookie(通常为 C:\Users\%USERNAME%\.erlang.cookie
  • 对于 RabbitMQ Windows 服务 - %USERPROFILE%\.erlang.cookie(通常为 C:\WINDOWS\system32\config\systemprofile

如果使用 Windows 服务,应将 cookie 从 C:\Windows\system32\config\systemprofile\.erlang.cookie 复制到运行诸如 rabbitmqctl.bat 等命令的用户所期望的位置。

使用 CLI 和运行时命令行参数进行覆盖

作为替代方案,可以将“-setcookie ”添加到 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 节点会记录错误,如“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

节点数量和仲裁

由于某些功能(如仲裁队列MQTT 中的客户端追踪)需要集群成员之间的共识,因此强烈建议使用奇数个集群节点:1、3、5、7 等。

强烈建议不要使用两个节点的集群,因为在连接丢失的情况下,集群节点无法确定多数并达成共识。例如,当两个节点失去连接时,MQTT 客户端连接将无法被接受,仲裁队列将失去可用性,等等。

从共识的角度来看,四节点或六节点集群的可用性特征与三节点和五节点集群相同。

仲裁队列指南更详细地涵盖了此主题。

集群与客户端

消息协议

假设所有集群成员都可用,消息传递(AMQP 0-9-1、AMQP 1.0、MQTT、STOMP)客户端可以连接到任何节点并执行任何操作。节点将透明地将操作路由到仲裁队列领导者,这对客户端来说是透明的。

对于所有受支持的消息协议,客户端一次只能连接到一个节点。

如果发生节点故障,客户端应能够重新连接到另一个节点、恢复其拓扑并继续操作。因此,大多数客户端库接受端点列表(主机名或 IP 地址)作为连接选项。该主机列表将在初始连接期间以及连接恢复期间(如果客户端支持)使用。有关详细信息,请参阅各个客户端的文档指南。

对于仲裁队列,客户端将只能在拥有大多数副本在线的队列上执行操作。

流客户端

RabbitMQ 流协议客户端的表现与消息传递协议客户端不同:它们更了解集群拓扑。对于发布,它们可以连接到任何节点,该节点会将所有相关操作转发到承载流领导者副本的节点。

但是,流消费者应连接到承载目标流副本的节点之一。该协议包含一个拓扑发现操作,因此表现良好的客户端库会选择合适的节点之一。然而,当使用负载均衡器时,情况就不会如此了。

请参阅流客户端连接指南以了解更多信息。

队列和流领导者副本放置

RabbitMQ 中的每个队列和流都有一个主要副本(对于经典队列,它是唯一副本)。该副本称为领导者。队列和流上的所有发布操作首先通过领导者副本,然后复制到跟随者(次要副本)。这对于保证消息的 FIFO 顺序是必要的。

为了避免集群中的某些节点承载绝大多数队列领导者副本从而承担大部分负载,队列领导者应在集群节点间相对均匀地分布。

队列领导者分布可以通过三种方式进行控制:

  1. 策略,通过设置 queue-leader-locator
  2. 配置文件,通过设置 queue_leader_locator
  3. 可选队列参数,通过设置 x-queue-leader-locator不推荐

有两个选项可用:

  • 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-localrandommin-masters。后两个值在内部现在被映射到 balanced

集群与可观测性

客户端连接、通道和队列将分布在集群节点上。操作员需要能够检查和监控集群所有节点上的此类资源。

RabbitMQ CLI 工具(如 rabbitmq-diagnosticsrabbitmqctl)提供检查资源和集群范围状态的命令。有些命令专注于单个节点的状态(例如 rabbitmq-diagnostics environmentrabbitmq-diagnostics status),其他命令则检查集群范围的状态。后者的示例包括 rabbitmqctl list_connectionsrabbitmqctl list_mqtt_connectionsrabbitmqctl list_stomp_connectionsrabbitmqctl list_usersrabbitmqctl list_vhosts 等。

此类“集群范围”命令通常会先联系一个节点,发现集群成员,然后联系所有节点以检索并结合它们各自的状态。例如,rabbitmqctl list_connections 将联系所有节点,检索它们的 AMQP 0-9-1 和 AMQP 1.0 连接,并将它们全部显示给用户。用户无需手动联系所有节点。假设集群状态未改变(例如没有连接关闭或打开),针对两个不同节点依次执行的两个 CLI 命令将产生相同或语义一致的结果。然而,“节点本地”命令不会产生相同的结果,因为两个节点很少有相同的状态:至少它们的节点名称是不同的!

管理界面的工作方式类似:必须响应 HTTP API 请求的节点会将其请求分发给其他集群成员并汇总它们的响应。在启用了管理插件的多个节点的集群中,操作员可以使用任何节点访问管理界面。使用 HTTP API 收集集群状态数据的监控工具也是如此。无需依次向每个集群节点发送请求。

节点故障处理

RabbitMQ 代理容忍单个节点的故障。只要它们在关机时能联系到已知的集群成员节点,节点就可以随时启动和停止。

仲裁队列允许队列内容在多个集群节点之间进行复制,具有并行复制以及可预测的领导者选举数据安全行为,只要大多数副本在线即可。

非复制的经典队列也可以在集群中使用。它们在发生节点故障时的行为取决于队列持久性

RabbitMQ 集群有几种处理网络分区的模式,主要面向一致性。集群旨在跨局域网 (LAN) 使用。不建议运行跨广域网 (WAN) 的集群。ShovelFederation 插件是连接跨广域网代理的更好解决方案。请注意,Shovel 和 Federation 不等同于集群

指标和统计信息

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

在较旧的(长期未维护的)版本中,RabbitMQ 管理插件使用专用节点进行统计数据的收集和聚合。

使用 rabbitmqctl 的集群操作记录

以下几个部分提供了手动设置和操作跨三台机器(rabbit1rabbit2rabbit3)的 RabbitMQ 集群的操作记录。建议在采用更适合自动化的集群形成选项之前,先研究该示例。

我们假设用户已登录到所有三台机器,RabbitMQ 已安装在这些机器上,并且 rabbitmq-serverrabbitmqctl 脚本位于用户的 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@,其中短节点名称是小写的(如上文中的 rabbit@rabbit1)。在 Windows 上,如果使用 rabbitmq-server.bat 批处理文件,则短节点名称是大写的(如 rabbit@RABBIT1)。在输入节点名称时,大小写很重要,这些字符串必须完全匹配。

在没有节点发现的情况下创建集群

重要

从 RabbitMQ 4.1.0 开始,节点在加入另一个节点之前不再需要停止和重置。

rabbitmqctl join_cluster 会执行必要的准备工作。

提示

考虑使用节点发现机制之一,而不是使用 CLI 工具进行手动集群形成。

为了将我们的三个节点链接在一起成为集群,我们告诉其中两个节点(比如 rabbit@rabbit2rabbit@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 秒。

如果对等节点在该时间间隔内可用,则节点成功启动,从对等节点同步所需内容并继续运行。

如果对等节点未变得可用,重启后的节点将放弃并主动停止。这种情况可以通过日志中的超时 (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

这个基本检查将允许部署继续进行,并且节点最终会重新加入对方(假设它们是兼容的)。

重启期间的主机名更改

在节点名称或主机名更改后重新加入的节点,如果其数据目录路径因此发生更改,则可能作为空白节点启动。此类节点将无法重新加入集群。当节点离线时,其对等节点可以被重置或以空白数据目录启动。在这种情况下,恢复中的节点也无法重新加入其对等节点,因为内部数据存储集群标识将不再匹配。

考虑以下场景

  1. 形成了一个由 3 个节点 A、B 和 C 组成的集群。
  2. 节点 A 被关闭。
  3. 节点 B 被重置。
  4. 节点 A 启动。
  5. 节点 A 尝试重新加入 B,但 B 的集群标识已更改。
  6. 节点 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@rabbit1rabbit@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_NODENAMERABBITMQ_NODE_PORTRABBITMQ_DIST_PORT,以及文件和目录位置指南中的 RABBITMQ_MNESIA_DIRRABBITMQ_CONFIG_FILERABBITMQ_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.026.1.2 可以混合使用,但 25.3.2.826.2.0 可能会引入节点间通信协议的破坏性更改。虽然这种破坏性更改很少见,但它是可能的。

Erlang/OTP 版本补丁发布之间的不兼容性非常罕见。

© . This site is unofficial and not affiliated with VMware.