RabbitMQ 集群:搭建、配置与最佳实践
解锁 RabbitMQ 集群的可扩展和弹性消息传递能力。本指南涵盖节点类型、网络分区和数据同步等核心概念。逐步学习如何搭建 RabbitMQ 集群、使用策略配置高可用(HA)队列,并实施稳健部署与管理的最佳实践。适合希望构建容错消息驱动型应用的开发者和运维人员。
RabbitMQ 集群:搭建、配置与最佳实践
RabbitMQ 集群常被误解。集群由一个逻辑代理组成,由多个 Erlang 节点构成。它在这些节点间共享用户、虚拟主机、交换机、绑定、策略及其他元数据。但它不会自动让每个队列的消息在所有地方都可用。队列的可用性取决于队列类型及其复制设置。
这种差异在生产环境中很重要。集群可以简化管理和路由,并支持高可用队列,但它并非神奇的性能开关。如果你将所有热队列放在一个节点上,该节点仍需承担所有工作。如果你使用未复制的经典队列,且队列领导者所在的节点消失,该队列将不可用,直到节点恢复。请根据实际运行的队列来设计集群。
集群共享的内容与不共享的内容
RabbitMQ 集群的元数据是复制的。如果你在一个节点上声明交换机,其他节点也会知道它。如果你添加用户或策略,集群会存储该定义。客户端应用可以连接到任何节点并使用相同的拓扑。
消息则不同。每个队列都有一个领导者。对于经典队列,消息存储在托管该队列的节点上,除非你使用旧的镜像队列。对于仲裁队列,RabbitMQ 使用共识协议在节点组中复制队列数据。对于流,数据根据流配置进行复制。在现代 RabbitMQ 部署中,仲裁队列通常是复制型持久工作队列的更安全选择。
旧文章常将“HA 队列”视为现代默认设置。在 RabbitMQ 术语中,这通常指通过策略配置的经典镜像队列。它们在某些安装中仍然存在,但仲裁队列是大多数新持久复制队列设计应考虑的方向。在迁移现有工作负载前,务必检查 RabbitMQ 版本和环境的操作约束。
加入节点前的准备
先完成这些基础检查:
- 节点必须能一致地解析彼此的主机名。
- Erlang 分发端口和 RabbitMQ 端口必须在节点间可达。
- 集群中 RabbitMQ 和 Erlang 版本应兼容。
- 所有节点必须共享相同的 Erlang cookie。
- 时间同步应合理,尤其当监控和 TLS 依赖它时。
Erlang cookie 是 Erlang 节点使用的共享密钥。在许多 Linux 包中,它位于 /var/lib/rabbitmq/.erlang.cookie,由 rabbitmq 用户拥有,权限为 600。
sudo systemctl stop rabbitmq-server
sudo install -o rabbitmq -g rabbitmq -m 600 .erlang.cookie /var/lib/rabbitmq/.erlang.cookie
sudo systemctl start rabbitmq-server
不要随意在运行中的集群上重新生成 cookie。如果某个节点使用不同的 cookie,它将无法与其他节点通信,且错误信息通常不友好。
加入节点
假设 rabbit@rmq-a 已在运行,rabbit@rmq-b 需要加入。在 rmq-b 上:
sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl join_cluster rabbit@rmq-a
sudo rabbitmqctl start_app
然后从任意节点验证:
rabbitmqctl cluster_status
rabbitmq-diagnostics cluster_status
reset 会在加入前移除本地节点的 RabbitMQ 数据库。这通常适用于新的空节点。不要随意在拥有重要队列的节点上运行此命令。
对于三个节点,从 rmq-c 重复相同过程。你可以将 rmq-b 和 rmq-c 都加入 rmq-a;一旦加入,元数据就没有人们有时想象的永久“主”节点。
将客户端置于稳定端点之后
如果期望进行节点维护,应用程序不应只有一个硬编码的代理主机。使用负载均衡器、DNS 策略或客户端库连接列表。负载均衡器应检查 RabbitMQ 应用程序是否在运行,而不仅仅是端口 5672 是否开放。
简单的 TCP 检查可能会将客户端发送到存活但被警报阻塞或未完全加入的节点。在更严格的环境中,使用管理插件暴露的健康检查或运行 rabbitmq-diagnostics -q ping 的本地检查。
有意识地选择队列类型
对于持久复制的工作负载,仲裁队列通常是好的默认选择:
rabbitmqadmin declare queue name=orders.pending durable=true arguments='{"x-queue-type":"quorum"}'
或通过应用程序声明:
channel.queue_declare(
queue='orders.pending',
durable=True,
arguments={'x-queue-type': 'quorum'}
)
仲裁队列以一定的吞吐量和延迟换取更强的复制行为。它们并非每个队列的免费升级。对于临时回复队列、短生命周期的扇出订阅者或低价值的临时工作,经典队列可能就足够了。对于必须承受节点丢失的业务事件,请使用复制队列类型并测试故障转移。
网络分区是操作事件,而非复选框
网络分区意味着集群节点无法全部相互通信。RabbitMQ 有分区处理策略,但没有一个能将损坏的网络变为健康网络。正确的应对方法是设计集群,使分区罕见、可见,并谨慎恢复。
对于大多数生产集群,使用奇数个节点来处理基于仲裁的工作负载,并避免在不可靠链路上拉伸小型集群。如果延迟可接受,三个节点分布在三个可用区可以很好地工作。两个节点分布在两个站点是常见痛苦决策的来源,因为链路中断时没有多数派。
怀疑分区后,检查:
rabbitmqctl cluster_status
rabbitmq-diagnostics alarms
rabbitmq-diagnostics check_running
rabbitmqctl list_queues name type leader members online state
如果队列领导者移动或成员离线,不要因为连接恢复就认为应用程序正常。监控发布者确认、消费者错误率和未确认消息。
防止集群意外的维护习惯
在可能的情况下,停止节点前先排空连接。如果客户端位于负载均衡器后,将节点从轮询中移除,等待客户端重新连接到其他地方,然后重启 RabbitMQ。
定期检查队列分布:
rabbitmqctl list_queues name type leader messages consumers
如果每个热队列的领导者都在一个节点上,集群对该工作负载来说是不平衡的。你可能需要重新声明队列、审查策略,或使用适合 RabbitMQ 版本的队列领导者定位器设置。
将策略置于版本控制之下。更改队列类型、死信、最大长度或镜像行为的策略是生产基础设施,而非 UI 调整。
备份仍然重要。集群不能替代定义导出、基础设施自动化或灾难恢复规划。在拓扑更改后导出定义:
rabbitmqadmin export rabbitmq-definitions.json
最后,测试你认为能承受的故障。停止持有队列领导者的节点。在消费者有未确认消息时杀死它。在暂存环境中磁盘警报期间阻止发布者。RabbitMQ 集群通过枯燥的演练赢得信任,而非通过一张包含三个节点的图表。