RabbitMQ 集群部署实战:三节点集群与队列高可用

在业务系统里,RabbitMQ 常承担订单、通知、日志收集这类异步流量。单节点测试环境够用,但节点宕机或停机升级时,未消费的消息与在途流量都会受影响。本文在三节点环境上部署 RabbitMQ 集群,重点讲清两件事:集群到底复制什么,队列高可用为什么选仲裁队列(Quorum Queue)而不是老一代经典镜像队列。环境口径:Rocky Linux 9、RabbitMQ 3.13、Erlang 26,节点 mq-01(192.0.2.11)、mq-02(192.0.2.12)、mq-03(192.0.2.13)。

前置条件

  • 三台主机网络互通,/etc/hosts 或 DNS 能解析 mq-01、mq-02、mq-03 到固定内网地址;集群通信用主机名而不是 IP。
  • 防火墙放行 4369(epmd)、5672(AMQP)、15672(管理界面)、25672(节点间通信)、35672–35682(CLI 与节点通信)与 15692(监控指标)。
  • 时间同步正常(chrony 已启用)。Erlang 通信对时钟偏差不敏感,但证书校验与日志排查依赖时间一致。
  • 准备好统一的 Erlang cookie 值(部署时生成随机字符串即可),三台必须完全一致——这是组集群的第一大坑。

先把集群模型讲清:复制的是元数据

RabbitMQ 集群最容易被误解的一点是:集群复制的是元数据,不是所有队列的内容。交换机、绑定关系、虚拟主机、用户与权限会同步到全部节点,任何节点可查;但普通经典队列(Classic Queue)的数据只存在于它声明时所在的节点上,其他节点只存一个指针。客户端连到非队列所在节点时消息会被透明转发;可一旦队列所在节点宕机,该队列在节点恢复前不可用,消息也不会自动出现在别的节点上。

要让队列本身高可用,历史方案是经典镜像队列(Mirrored Queue),靠策略把队列内容镜像到多个节点。但这套机制基于早期的一致性实现,网络分区下行为不理想,官方已列为弃用特性,新部署不要再用。当前的正解是仲裁队列:它基于 Raft 协议,队列数据以日志形式复制到多数派节点,写入需多数派确认,少数派节点宕机时队列仍可读写,语义清晰可预期。另外,老教程的 disc/ram 节点区分在新版本已失去意义,默认全部用 disc 节点。

第一步:安装并统一 Erlang cookie

三台安装相同版本的 Erlang 与 RabbitMQ,混版本集群是故障高发区。装完先不要急着组集群,第一件事是统一 cookie:

# 三台版本保持一致(以官方仓库安装为例)
sudo dnf install -y erlang rabbitmq-server
sudo systemctl enable rabbitmq-server

# 在 mq-01 上查看 cookie 内容,其余两台替换为完全相同的值
sudo cat /var/lib/rabbitmq/.erlang.cookie
sudo chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie
sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie

cookie 是 Erlang 节点间互相认证的令牌,内容不一致时 join 会直接报认证失败。属主必须是 rabbitmq 用户、权限必须 400,改完重启服务生效。

第二步:组建集群

先只在 mq-01 上启动服务并开启管理与监控插件作为种子节点,再让 mq-02、mq-03 依次加入。加入的本质是四步:停应用、重置本地状态、向种子节点注册、再启动应用。

# mq-01:启动并启用管理、监控插件
sudo systemctl start rabbitmq-server
sudo rabbitmq-plugins enable rabbitmq_management rabbitmq_prometheus

# mq-02、mq-03 上分别执行
sudo systemctl start rabbitmq-server
sudo rabbitmqctl stop_app
sudo rabbitmqctl reset
sudo rabbitmqctl join_cluster rabbit@mq-01
sudo rabbitmqctl start_app

# 任意节点查看组建结果
sudo rabbitmqctl cluster_status

注意 join_cluster 用 rabbit@主机名形式的节点名,主机名必须能被其他节点解析,这正是强调 /etc/hosts 的原因;用 IP 硬编码节点名,后续换机器、改网段会寸步难行。

第三步:两个必须改的关键配置

默认参数有两处必须改:网络分区策略与磁盘告警阈值。分区策略推荐 pause_minority——分区时少数派一侧节点自动暂停应用,避免两侧同时写入造成数据分叉;磁盘阈值决定磁盘将满时从何时开始阻断发布者,留足余量避免盘被队列数据写死。

# /etc/rabbitmq/rabbitmq.conf
cluster_partition_handling = pause_minority
disk_free_limit.absolute = 2GB
vm_memory_high_watermark.relative = 0.6

配置修改后逐台滚动重启生效,不要三台同时重启。内存达到 vm_memory_high_watermark 时发布连接会被临时阻塞(block),这是保护而非故障,但监控要把这条线画出来,免得发布变慢就被当故障报上来。

第四步:用户、虚拟主机与仲裁队列

按业务划分虚拟主机并建最小权限用户,默认的 guest 用户只能本机登录,但仍建议删除。仲裁队列在声明时由客户端用 x-queue-type 参数指定,或用策略给一类队列批量套用:

sudo rabbitmqctl add_vhost orders
sudo rabbitmqctl add_user appuser '请替换为强口令'
sudo rabbitmqctl set_permissions -p orders appuser "^orders\." "^orders\." "^orders\."
sudo rabbitmqctl delete_user guest

# 给以 qq. 开头的队列统一套用仲裁队列类型
sudo rabbitmqctl set_policy -p orders qq-default "^qq\." '{"queue-type":"quorum"}' --priority 10 --apply-to queues

客户端不要直连单节点:前面挂 HAProxy 做统一入口,或用支持多地址与断线重连的客户端库,连接配置写全三个节点地址。仲裁队列副本数建议保持 3 个——太少失去高可用意义,太多则写入延迟被多数派里最慢的成员拖住。

第五步:节点维护与下线

滚动维护的标准动作是先 stop_app,再升级或改配置,最后 start_app 并等待完成。仲裁队列在成员下线期间会自动在存活成员里重新选主,业务侧只感知短暂重连。节点永久退役且还能启动时,先 stop_app 再在其他节点移除;已损毁无法启动的,直接在存活节点上遗忘掉:

# 维护单个节点
sudo rabbitmqctl stop_app
sudo rabbitmqctl start_app
sudo rabbitmqctl await_startup

# 彻底移除已损毁节点(在存活节点上执行)
sudo rabbitmqctl forget_cluster_node rabbit@mq-03

# 下线前确认该节点不是某些仲裁队列的唯一多数派成员
sudo rabbitmq-queues check_if_node_is_quorum_critical rabbit@mq-02

监控日常盯三类指标:连接与通道数(连接泄漏先在这里暴露)、各队列待消费与未确认消息数(看积压趋势比看瞬时值重要)、节点磁盘与内存告警。rabbitmq_prometheus 在 15692 端口暴露指标,接进现有 Prometheus 即可。

验证

  • cluster_status 里三台均为 disc 节点,running_nodes 包含全部三台,alarms 为空。
  • 仲裁队列的成员分布与 Leader 位置符合预期:
sudo rabbitmqctl list_queues -p orders name type messages_ready messages_unacknowledged consumers
sudo rabbitmq-queues quorum_status -p orders qq.orders
  • 故障演练:在 mq-02 上 stop_app,观察 qq.orders 的 Leader 漂移,客户端应能继续发布消费且无消息丢失;再 start_app 让其回归。
  • 管理界面(15672)与指标端点(15692)可正常访问。

常见问题

1. 网络分区后两侧都在收消息

这是分区策略没配或配成 ignore 的典型后果。pause_minority 模式下少数派节点自动暂停,分区恢复后重启应用重新加入;仲裁队列以多数派一侧的日志为准自动追平。若还是经典队列或旧镜像队列,分区期间两侧写入可能互相覆盖甚至丢失——这也是必须用仲裁队列的原因。分区后先看各节点 cluster_status 与告警,再动业务流量。

2. 磁盘告警触发,发布者全部被阻塞

可用磁盘低于 disk_free_limit 时,节点拉起磁盘告警并阻塞全部发布连接,消费不受影响。先用 rabbitmq-diagnostics check_local_alarms 确认告警,再按顺序处理:清日志与无关文件、消费或转移积压消息、确认大队列的增长原因。不要图省事调低阈值硬撑,盘被写满后损坏的就不只是消息了。空间回到阈值以上,告警自动解除、发布自动恢复。

3. 升级某个节点后无法重新加入,报特性标志不兼容

集群内升级遵循滚动规则:一台一台升,全部升到同一版本后,再在任一节点执行 rabbitmqctl enable_feature_flag all 打开新特性。跨大版本必须走官方升级路径,不要跳版本直升;旧版本节点去 join 高版本集群,也会在特性标志这一步被拦下。先升仲裁队列的从成员、最后升 Leader 所在节点,把选主抖动降到最小。

4. join_cluster 报认证失败或连不上目标节点

先查三样:.erlang.cookie 是否逐字节一致(注意结尾换行)、属主与 400 权限是否正确、4369 与 25672 端口是否互通且主机名可互解析,其中 cookie 不一致占绝大多数。都正常还连不上,再在目标节点用 rabbitmqctl status 确认服务在运行。

小结

三节点加仲裁队列加 pause_minority,是当前 RabbitMQ 高可用的标准组合。记住三条:元数据全集群复制、队列数据靠仲裁队列复制、连接层做多节点接入与重连。再把磁盘与内存水位、队列积压纳入监控,集群才算从“装上了”变成“可用了”。

发表回复

后才能评论