单机部署请移步: RabbitMQ 部署及配置详解 (单机)
RabbitMQ 集群是一个或 多个节点,每个节点共享用户、虚拟主机、 队列、交换、绑定、运行时参数和其他分布式状态。
一、RabbitMQ 集群可以通过多种方式形成:
-
通过在配置文件中列出群集节点以声明方式
-
以声明方式使用基于 DNS 的发现
-
以声明方式使用 AWS (EC2) 实例发现(通过插件)
-
声明式使用 Kubernetes 发现(通过插件)
-
以声明方式使用基于 Consul 的发现(通过插件)
-
声明式地使用基于 etcd 的发现(通过插件)
-
手动使用 rabbitmqctl
二、集群部署的核心概念:
1、 仲裁队列
仲裁队列是 RabbitMQ 实现持久、 基于Raft共识算法的复制FIFO队列。 它从 RabbitMQ 3.8.0 开始可用。
仲裁队列和流取代了持久镜像队列,即原始队列 复制的队列类型,现已弃用并计划删除。
仲裁队列针对数据安全性为 重中之重。这在动机中有所介绍。 仲裁队列应被视为复制队列类型的默认选项。
与传统镜像队列相比,仲裁队列在行为上也存在重要差异和一些限制, 包括特定于工作负载的,例如,当使用者重复对同一消息重新排队时。另外仲裁队列支持死信交换 (DLX)。
仲裁队列和镜像队列差异
2、节点名称
RabbitMQ 节点由节点名称标识。节点名称由两部分组成, 前缀(通常是兔子)和主机名。例如,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 选项 必须指定。
3、集群形成要求
主机名解析
RabbitMQ 节点使用节点名称(组合)相互寻址 前缀和域名,可以是短的还是完全限定的 (FQDN)。因此,每个集群成员都必须能够解析主机名 对于所有其他集群成员,其自己的主机名也是如此 作为可能使用 RabbitMQCTL 等命令行工具的计算机。节点将在节点引导时尽早执行主机名解析。 在基于容器的环境中,主机名很重要 在容器启动之前,解决方案已准备就绪。 对于 Kubernetes 用户来说,这意味着 CoreDNS 的 DNS 缓存间隔值在 5-10 秒范围内。
主机名解析可以使用任何标准操作系统提供 方法:
-
域名系统记录
-
本地主机文件(例如 /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 节点和 CLI 工具(例如 rabbitmqctl)使用 cookie 以确定是否允许他们与 彼此。为了使两个节点能够通信,它们必须具有 同样的共享密钥称为 Erlang cookie。cookie只是一个字母数字字符的字符串,最大为 255 个字符。 它通常存储在本地文件中。该文件必须仅 所有者可访问(例如,具有 600 或类似的 UNIX 权限)。 每个群集节点必须具有相同的 Cookie。如果该文件不存在,Erlang VM 将尝试创建 一个在 RabbitMQ 服务器时随机生成的值 启动。在开发中使用此类生成的 cookie 文件是合适的 仅限环境。在 UNIX 系统上,cookie 通常为 位于 /var/lib/rabbitmq/.erlang.cookie
三、部署
1、启动独立节点
RabbitMQ CLI 工具(如 rabbitmq-diagnostics 和 rabbitmqctl)提供了检查资源和集群范围状态的命令。
通过重新配置现有 RabbitMQ 来设置集群 节点加入群集配置。因此,第一步 就是以正常方式在所有节点上启动 RabbitMQ:
# on rabbit1
rabbitmq-server -detached
# on rabbit2
rabbitmq-server -detached
# on rabbit3
rabbitmq-server -detached
这将创建三个独立的 RabbitMQ 代理, 每个节点上一个,如 cluster_status 命令所确认的那样:
# on rabbit1
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit1 ...
# => [{nodes,[{disc,[rabbit@rabbit1]}]},{running_nodes,[rabbit@rabbit1]}]
# => ...done.
# on rabbit2
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit2 ...
# => [{nodes,[{disc,[rabbit@rabbit2]}]},{running_nodes,[rabbit@rabbit2]}]
# => ...done.
# on rabbit3
rabbitmqctl cluster_status
# => Cluster status of node rabbit@rabbit3 ...
# => [{nodes,[{disc,[rabbit@rabbit3]}]},{running_nodes,[rabbit@rabbit3]}]
# => ...done.
从 rabbitmq-server shell 脚本启动的 RabbitMQ 代理的节点名称是 rabbit@shorthostname,其中 short 节点名称为小写(如rabbit@rabbit1, 以上)。在 Windows 上,如果使用 rabbitmq-server.bat 批处理文件,则短节点名称为大写(如 在rabbit@RABBIT1)。键入节点名称时, 大小写很重要,这些字符串必须完全匹配
2、创建集群
为了将集群中的三个节点链接起来,我们告诉 其中两个节点,例如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.
通过执行上述步骤,我们可以在集群运行时随时向集群添加新节点。
3、重新启动群集节点
已加入群集的节点可以随时停止。它们也可能出现故障或被操作系统终止。
通常,如果节点停止后大多数节点仍处于联机状态,这不会影响集群的其余部分,尽管集群的客户端连接分布、队列副本放置和负载分布会发生变化。
4、来自联机对等方的架构同步
重新启动的节点将在启动时同步来自其对等节点的架构和其他信息。在这个过程完成之前,节点将不会完全启动和运行。
因此,了解流程节点在停止和重新启动时所经历的过程非常重要。
停止节点在重新启动后选择要同步的联机群集成员(只考虑磁盘节点)。在重新启动时,节点将尝试默认联系该对等方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分钟以上才能完成的集群范围内的重新部署场景。
在升级过程中,有时最后一个停止的节点必须是升级后第一个启动的节点。该节点将被指定执行集群范围的模式迁移,其他节点可以从该迁移同步并在重新加入时应用。
5、重新启动和运行状况检查(就绪探测器)
在某些环境中,通过指定的运行状况检查来控制节点的重新启动。检查验证一个节点是否已启动,并且部署过程可以继续到下一个节点。如果检查不通过,则认为节点的部署不完整,部署过程通常会等待一段时间并重试。这种环境的一个流行例子是Kubernetes,在这里,当使用OrderedReady 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
这种基本检查将允许部署继续进行,并允许节点最终重新加入彼此,假设它们是兼容的。
6、在重启时修改主机名
如果节点名称或主机名更改后重新加入的节点的数据目录路径因此而更改,则该节点可以作为空白节点启动。这样的节点将无法重新加入集群。当节点处于脱机状态时,其对等节点可以重置或使用空白数据目录启动。在这种情况下,恢复节点也将无法重新加入其对等节点,因为内部数据存储集群标识将不再匹配。
考虑以下情况:
-
形成由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。