先别急着抄配置,咱们得把一个问题掰开:RabbitMQ 的高可用,到底要防什么?单节点消息队列看起来一切正常,消息能发能收,监控也不报警,但它就是个随时会爆的单点。生产环境遇到 Erlang VM 崩溃、磁盘被日志占满、云主机被回收,队列数据只要没有副本,你面对的就是一边业务堆积、一边恢复无门。RabbitMQ 高可用要防的,是这类不像日常却一定会发生的故障。
下面这套方案适合两类人:一是已经会用 RabbitMQ 基本功能、但没真正搭过集群的后端开发;二是消息量不算大、却被业务要求 7×24 不退服的小团队。整体思路不复杂:用三节点 RabbitMQ 集群解决数据冗余,用 HAProxy 作为统一入口解决负载均衡和故障摘除,跑通之后,整个验证过程控制在 10 分钟左右。
1. 先想明白:RabbitMQ 的高可用到底缺什么
1.1 单节点消息队列,挂掉的不只是服务
很多人觉得 RabbitMQ 单节点也没问题,因为消息都放在磁盘上,进程挂了拉起来就行。但真到故障时你会发现,问题不止是“RabbitMQ 进程没了”,而是业务链路在等它恢复。
消费者连不上,消息生产者不断重试,数据库连接池和线程池被打满,最后整个服务雪崩。哪怕是几秒钟的不可用,对支付通知、订单状态这类实时性要求高的场景都是大事故。单节点还有一个隐蔽问题:磁盘写满、内存不足、分区检测触发,这三个状态会让 RabbitMQ 进入“假死”模式——端口还开着,但生产者被阻塞,管理员在 UI 上也看不出太多异常。高可用不是给机器买保险,而是给这类故障提前铺好退路。
1.2 集群负责存数据,HAProxy 负责管入口
RabbitMQ 本身支持集群,多个节点组成一个逻辑集群,队列可以在多个节点上保留副本。经典做法是镜像队列,新版本则推荐 Quorum Queue。但这些机制解决的是“数据不丢”的问题,并没有解决“客户端从哪个入口连”的问题。
你不可能让业务代码自己在 RabbitMQ 节点之间切换。比如有 3 个节点,客户端写死连 mq1,mq1 一旦宕机,就算消息在 mq2、mq3 上有副本,客户端也连接不上。真正高可用的拓扑一定是:集群节点对外只暴露内网地址,前面放一个接入层,由接入层统一对外提供 5672 端口,并负责健康检查、流量分发和故障摘除。接入层这一角色,HAProxy 做得最顺手。
1.3 为什么选 HAProxy,而不是 Nginx 或 LVS
Nginx 有 stream 模块,也能做四层转发,但健康检查能力相对原始,长连接监控不如 HAProxy 直观。LVS 性能很强,但要配合 keepalived,配置和运维成本也更高,适合大型流量入口。HAProxy 在这类场景里的优势是:配置简单、天生支持 TCP 模式、内置健康检查和实时统计页,对 AMQP 这类二进制长连接协议非常友好。RabbitMQ 官方文档里给的高可用示例也经常用 HAProxy,社区里踩过坑的人多,资料好找。
注意:如果只是搭着玩,一台 HAProxy 也够。生产环境要再给 HAProxy 做双机 Keepalived,这件事放到后面再展开。
2. 用 Docker 快速搭一个三节点 RabbitMQ 集群
2.1 拓扑和端口规划
先看一下整体拓扑。三台 RabbitMQ 节点在同一个 Docker 网络里,对外不直接暴露业务端口,只有 HAProxy 暴露端口。客户端统一连接 HAProxy 的 5672 和 15672,由 HAProxy 分发到后端。
| 角色 | 服务 | 端口 | 说明 |
|---|---|---|---|
| RabbitMQ 节点 | rabbitmq | 5672/tcp | AMQP 客户端连接 |
| RabbitMQ 节点 | rabbitmq | 15672/tcp | 管理 UI / HTTP API |
| RabbitMQ 节点 | rabbitmq | 25672/tcp | 节点间通信(Docker 内网) |
| HAProxy | haproxy | 5672/tcp | 对外 AMQP 入口 |
| HAProxy | haproxy | 15672/tcp | 对外管理界面入口 |
| HAProxy | haproxy | 8404/tcp | HAProxy 统计页 |
生产环境里,RabbitMQ 节点之间不要跨公网组集群,Erlang 节点通信对延迟和网络分区都很敏感。三台节点最好在同一个机房或同一个可用区。
2.2 一份可直接抄的 docker-compose.yml
下面这份 Compose 配置把三个 RabbitMQ 节点和一个 HAProxy 放在同一个网络里,RabbitMQ 节点之间用固定 hostname 通信,Erlang Cookie 保持一致。
services: rabbit1: image: rabbitmq:3.13-management hostname: rabbit1 restart: unless-stopped environment: RABBITMQ_ERLANG_COOKIE: "mq-cluster-cookie-2025" volumes: - mq1data:/var/lib/rabbitmq networks: - mq-net rabbit2: image: rabbitmq:3.13-management hostname: rabbit2 restart: unless-stopped environment: RABBITMQ_ERLANG_COOKIE: "mq-cluster-cookie-2025" volumes: - mq2data:/var/lib/rabbitmq networks: - mq-net rabbit3: image: rabbitmq:3.13-management hostname: rabbit3 restart: unless-stopped environment: RABBITMQ_ERLANG_COOKIE: "mq-cluster-cookie-2025" volumes: - mq3data:/var/lib/rabbitmq networks: - mq-net haproxy: image: haproxy:2.8 restart: unless-stopped depends_on: - rabbit1 - rabbit2 - rabbit3 ports: - "5672:5672" - "15672:15672" - "8404:8404" volumes: - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro networks: - mq-net volumes: mq1data: mq2data: mq3data: networks: mq-net:镜像这里用3.13-management举例,如果你用的是 RabbitMQ 4.x,直接换成对应带management标签的镜像即可,但要注意 4.x 对镜像队列策略已经发生了变化,后面会单独说。
2.3 集群组网:Erlang Cookie 和 join_cluster
先把容器跑起来。
docker compose up -d docker compose psRabbitMQ 集群依赖 Erlang Cookie,所有节点的 Cookie 必须一致,否则join_cluster会直接报认证失败。上面 Compose 已经通过环境变量统一了 Cookie。接下来把 rabbit2 和 rabbit3 加入 rabbit1 所在的集群。
docker compose exec rabbit1 rabbitmqctl status docker compose exec rabbit2 rabbitmqctl stop_app docker compose exec rabbit2 rabbitmqctl reset docker compose exec rabbit2 rabbitmqctl join_cluster rabbit@rabbit1 docker compose exec rabbit2 rabbitmqctl start_app docker compose exec rabbit3 rabbitmqctl stop_app docker compose exec rabbit3 rabbitmqctl reset docker compose exec rabbit3 rabbitmqctl join_cluster rabbit@rabbit1 docker compose exec rabbit3 rabbitmqctl start_appstop_app是停掉 RabbitMQ 应用但保留 Erlang VM,reset清空节点本地的集群状态,join_cluster之后start_app重新启动应用。如果发现reset提示数据库有数据,不要慌,新节点的数据本来就是空的,reset会清掉默认初始状态。
检查集群状态:
docker compose exec rabbit1 rabbitmqctl cluster_status如果能列出三个节点,并且running_nodes是三个,说明集群组好了。
2.4 队列高可用策略:镜像队列和 Quorum Queue 怎么选
RabbitMQ 3.x 里,经典镜像队列可以通过策略开启副本同步。
docker compose exec rabbit1 rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'这句策略会把所有队列都镜像到集群所有节点。测试环境方便,但生产环境不建议对全量队列开all,因为每个节点都会存完整副本,磁盘占用直接翻倍。更好的做法是指定队列名前缀,比如:
docker compose exec rabbit1 rabbitmqctl set_policy ha-work "^ha\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'如果你用的是 RabbitMQ 3.8 以上,我更推荐直接使用 Quorum Queue。它的原理是基于 Raft 共识算法,数据自动复制到大多数节点,节点故障后能自动重新选主,对网络分区的容忍度也比镜像队列好。声明队列时显式指定类型:
docker compose exec rabbit1 rabbitmqctl add_vhost /myapp docker compose exec rabbit1 rabbitmqctl add_user mqadmin 'ChangeMe123!' docker compose exec rabbit1 rabbitmqctl set_user_tags mqadmin administrator docker compose exec rabbit1 rabbitmqctl set_permissions -p /myapp mqadmin ".*" ".*" ".*" docker compose exec rabbit1 rabbitmqctl set_permissions -p / mqadmin ".*" ".*" ".*"这段命令同时创建了虚拟主机/myapp和账号mqadmin,并给了全部权限。很多人在 Docker 部署 RabbitMQ 后,明明创建了 admin 账号却登录不了管理界面,或者登录后不能创建虚拟主机,基本都是因为少了set_user_tags administrator和set_permissions这两步。
3. 手写 HAProxy 配置:AMQP、管理 UI、Stats 一网打尽
3.1 AMQP 接入层最简配置
先创建haproxy.cfg,放在 Compose 文件同目录下。
global log stdout format raw local0 info maxconn 4096 defaults log global mode tcp timeout connect 5s timeout client 120s timeout server 120s timeout check 3s listen rabbitmq_amqp bind *:5672 mode tcp balance leastconn option tcplog maxconn 2048 server mq1 rabbit1:5672 check inter 3s fall 3 rise 2 server mq2 rabbit2:5672 check inter 3s fall 3 rise 2 server mq3 rabbit3:5672 check inter 3s fall 3 rise 2这段配置的核心是mode tcp。AMQP 是二进制协议,不是 HTTP,不能用七层转发去解析,否则连接会被破坏。balance leastconn会把新连接调度到当前连接数最少的节点,适合 AMQP 这种长连接场景。
check inter 3s fall 3 rise 2表示每 3 秒做一次 TCP 健康检查,连续失败 3 次标记为 DOWN,连续成功 2 次标记回 UP。注意timeout client和timeout server都设成了 120 秒,这个值一定要大于客户端的心跳间隔,否则客户端发心跳的间隔超过 HAProxy 的空闲超时,连接会被误杀。
3.2 管理界面和实时统计页
RabbitMQ 管理界面也需要从统一入口访问,可以在同一个配置文件里再加一段。
listen rabbitmq_web bind *:15672 mode tcp balance roundrobin option tcplog server mq1 rabbit1:15672 check inter 3s fall 3 rise 2 server mq2 rabbit2:15672 check inter 3s fall 3 rise 2 server mq3 rabbit3:15672 check inter 3s fall 3 rise 2 listen stats bind *:8404 mode http stats enable stats uri /stats stats refresh 5s stats auth admin:stats-pass-2025管理界面这里我用的是 TCP 四层转发。管理 UI 本身是 HTTP,对负载均衡的检测要求没那么高,四层转发简单可靠。如果你想要 HTTP 级探活,需要额外确认 RabbitMQ 的/api/health/checks/alarms接口在带认证请求下是否返回 200,不然 HAProxy 会把 401 当成失败节点,误判就很尴尬。
stats是 HAProxy 自带的实时统计页,访问http://localhost:8404/stats,输入配置里的账号密码,就能看到每个后端的连接数、健康状态和字节数,排查流量不均非常有用。
3.3 负载均衡算法怎么选,要不要粘性会话
RabbitMQ 的连接和普通 HTTP 请求不一样,一个连接会一直存在,可能挂几小时甚至几天。如果按 roundrobin 硬轮询,重启过一批节点后连接数很容易偏斜。所以 AMQP 入口更适合leastconn,让新连接尽量落在当前连接少的节点上。
管理界面短连接比较多,用 roundrobin 就够了,没必要上 leastconn。
粘性会话的问题经常被人问。我的看法是:AMQP 不需要刻意做粘性。HAProxy 只是四层转发,它不可能把一个已经建立的 TCP 连接“无缝迁移”到别的节点。就算按客户端 IP 做粘性,节点宕机时当前连接照样会断,最终还是得靠客户端自动重连。反而在出口 NAT 环境下,按 IP 粘性会把大量客户端散到同一个节点,可能造成新的流量倾斜。
3.4 健康检查的边界:端口检查不等于业务健康
HAProxy 默认的check做的是 TCP 端口探活,能发现节点进程挂了、端口不通这类问题,但它发现不了更深层的故障。比如 RabbitMQ 发生了磁盘告警、内存告警,端口可能还开着,业务却被阻塞;再比如节点处于网络分区状态,Erlang VM 还活着,但队列无法正常提供服务。
所以这套方案里,HAProxy 负责的是“入口摘除”,真正的高可用兜底还要靠 RabbitMQ 集群内部的 Quorum Queue 和客户端的自动恢复机制。别把 HAProxy 的端口检查当成业务健康检查,生产环境最好再配合 RabbitMQ 的监控指标和告警。
4. 实战验证:发布消息、杀节点、看流量切换
4.1 先让集群起来
配置写完,重新加载 Compose。
docker compose up -d docker compose exec haproxy haproxy -c -f /usr/local/etc/haproxy/haproxy.cfghaproxy -c是只检查配置不启动,如果配置有语法错误,这里会直接报错。确认通过后再访问管理界面http://localhost:15672,会自动被 HAProxy 转发到三台 RabbitMQ 中的一台。
4.2 用一段 Python 代码打流量
给应用创建队列并发送消息,先安装 pika。
pip install pika我这里以 Python 为例,演示消息通过 HAProxy 进入 RabbitMQ 集群。
import pika credentials = pika.PlainCredentials("mqadmin", "ChangeMe123!") params = pika.ConnectionParameters( host="127.0.0.1", port=5672, virtual_host="/myapp", credentials=credentials, heartbeat=60, blocked_connection_timeout=30, ) connection = pika.BlockingConnection(params) channel = connection.channel() channel.queue_declare( queue="ha.test", durable=True, arguments={"x-queue-type": "quorum"}, ) for i in range(50): channel.basic_publish( exchange="", routing_key="ha.test", body=f"msg-{i}".encode(), properties=pika.BasicProperties(delivery_mode=2), ) print("50 messages sent") connection.close()这段代码里最关键的是arguments={"x-queue-type": "quorum"},显式声明队列为 Quorum Queue。如果不用这个参数,在 3.x 里默认可能还是经典队列。
4.3 故障注入:停掉一个 RabbitMQ 节点会发生什么
验证高可用最直接的办法,就是真的杀掉一个节点。
docker compose stop rabbit1这时候从 HAProxy 统计页上能看到 mq1 在几秒内被标记为 DOWN。HAProxy 不会再给 mq1 分配新连接,已经建立的连接如果正好在 mq1 上,会被断开,客户端会收到连接异常。
继续运行刚才的 Python 脚本,消息依然能发出去。因为三节点集群里至少还有两个节点,Quorum Queue 依然满足多数派原则,队列可以正常选主和写入。如果用的是 ha-mode=all 镜像队列,剩余节点上也有完整副本,消息同样能继续收发。
但要注意一点:如果客户端没有开启自动恢复,宕机瞬间正在工作的消费者会直接抛异常,而且不会自动重连。这一点是本方案里最容易忽略的地方。
4.4 通过 Stats 页面确认摘除和恢复
打开http://localhost:8404/stats,登录后找到rabbitmq_amqp这一行。当 mq1 停掉后,它的状态会从 UP 变为 DOWN;重新启动节点后:
docker compose start rabbit1HAProxy 连续检查成功 2 次后,会把这个节点重新标记为 UP。整个过程大概 6 到 10 秒,不会立刻恢复。你还会看到 rabbit1 重新加入集群,但只有 HAProxy 的检查恢复后,它才会开始接收新连接。
5. 避坑实录:Virtual Host、账号权限、客户端自动恢复
5.1 admin 账号“能用”的前置条件
Docker 部署 RabbitMQ 后,最常见的坑是你用guest/guest登录管理界面,发现系统提示只能 localhost 访问。这是 RabbitMQ 自带的安全限制:guest 账号只允许通过本机访问。Docker 容器里的 RabbitMQ 对宿主机来说算远程地址,所以永远不要靠 guest 账号对外提供服务。
正确做法是创建独立账号,并给账号打上administrator标签:
docker compose exec rabbit1 rabbitmqctl add_user mqadmin 'ChangeMe123!' docker compose exec rabbit1 rabbitmqctl set_user_tags mqadmin administrator日志里出现Most detailed message was ""时,先检查是不是账号密码错了。这类问题不是高可用配置的问题,但会浪费你很多时间。
5.2 Virtual Host 权限不是可选项
RabbitMQ 的权限粒度是按虚拟主机划分的。只建账号、不给虚拟主机授权,客户端连接时会直接报ACCESS_REFUSED。正确姿势是把应用用的虚拟主机、账号、权限一次性配好:
docker compose exec rabbit1 rabbitmqctl add_vhost /myapp docker compose exec rabbit1 rabbitmqctl set_permissions -p /myapp mqadmin ".*" ".*" ".*"三个.*分别对应 configure、write、read 权限,覆盖队列管理、消息发送和消息消费。如果管理 UI 需要跨虚拟主机查看指标,记得在自己的主虚拟主机上也有权限。
5.3 客户端不开启自动恢复,HAProxy 也救不了你
HAProxy 能把新连接路由到好节点,但它无法替业务代码处理“旧连接断了之后重连”。很多客户端默认不开启自动恢复,尤其是自己封装的连接池,节点一切换,连接池里的连接全部失效。
以 .NET 官方客户端为例:
var factory = new ConnectionFactory { HostName = "127.0.0.1", Port = 5672, UserName = "mqadmin", Password = "ChangeMe123!", VirtualHost = "/myapp", AutomaticRecoveryEnabled = true, TopologyRecoveryEnabled = true };Java 客户端也是同理:
ConnectionFactory factory = new ConnectionFactory(); factory.setHost("127.0.0.1"); factory.setUsername("mqadmin"); factory.setPassword("ChangeMe123!"); factory.setVirtualHost("/myapp"); factory.setAutomaticRecoveryEnabled(true); factory.setTopologyRecoveryEnabled(true);Python 的 pika 没有像 Java/.NET 客户端那样完整的自动恢复体系,我的习惯是业务侧捕获连接异常后按退避策略重连,同时配合消费端的basic_ack保证不丢消息。高可用是端到端的事情,只把后端做成集群,客户端不配合,照样白搭。
5.4 镜像队列策略和 Quorum Queue 的坑
RabbitMQ 3.x 的镜像队列策略虽然方便,但有个坑:一切换到新版 4.x,镜像队列的ha-mode策略可能直接失效。4.x 把主推方向完全放到了 Quorum Queue 上,老的镜像队列模式被视为遗留方案。如果你的项目要长期维护,尽早切到 Quorum Queue。
Quorum Queue 也有自己的限制,比如不支持exclusive队列,部分插件特性不兼容。通常业务消息场景完全够用,但上线前还是要把官方文档里的限制扫一遍,尤其是消息顺序、消费语义和队列声明参数这些问题,别等线上出了问题再回头查。
6. 再往前走一步:双 HAProxy、监控与容量规划
6.1 HAProxy 自身也要高可用
用一台 HAProxy 做接入层,RabbitMQ 集群高可用了,但 HAProxy 本身还是单点。生产环境要做双 HAProxy + Keepalived 虚拟 IP,一台主节点挂掉后,虚拟 IP 自动漂移到备用节点。Keepalived 的配置核心就两块:
vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 virtual_ipaddress { 192.168.1.100 dev eth0 } }备用节点同样声明VI_1,但state改成BACKUP,priority改成 90。客户端连接时不再使用具体某台 HAProxy 的 IP,而是使用虚拟 IP192.168.1.100。这样 RabbitMQ 集群和接入层都不存在单点,整个链路才算完整。
6.2 监控、告警和日常巡检
HAProxy 统计页能看到连接数,但 RabbitMQ 内部情况还是要靠 RabbitMQ 本身的监控。建议开启 Prometheus 插件,收集队列深度、节点状态、Erlang 进程数等指标。告警至少覆盖这三类:
| 指标 | 严重级别 | 说明 |
|---|---|---|
| 节点 down | 紧急 | 集群至少一个节点不可用 |
| 队列堆积 | 警告 | 消息持续积压,消费者处理不过来 |
| 内存/磁盘水位 | 紧急 | 触发流量阻塞会直接影响业务 |
我用得比较多的巡检命令是rabbitmq-diagnostics -q check_alarms和rabbitmqctl cluster_status。前者看水位告警,后者看节点是否真的在同一集群里,网络分区问题早期大多能从这里发现。
6.3 容量规划与扩展的几点提醒
三节点是一个比较理想的起点,Quorum Queue 需要多数派节点才能工作,所以生产集群不要用 2 节点,奇数节点数是更可靠的选择。节点数继续扩容到 5 或 7 时,要记得不是所有队列都需要全部副本,副本越多,写入放大越明显,磁盘和网络压力都会增加。
HAProxy 的maxconn也要跟着评估。每个 AMQP 连接会占一点内存,连接数一多,HAProxy 本身的文件描述符和内存就成了瓶颈。上线前做一次连接数压力测试,比事后加机器有用得多。
最后分享一个我自己被坑过很多次才养成的习惯:每次搭完高可用方案,不是看配置写得多漂亮,而是真正拔电测试。停一个节点、再停一个节点、重启一个节点,让生产消费者脚本完整跑一轮,确认客户端能自动恢复、队列能重新选主,这套方案才敢说出可交付。HAProxy 配置只是第一步,把故障演练常态化,才是高可用最值钱的那部分。