RabbitMQ 是如何保证高可用的?
结论:高可用分三层——节点层用集群共享元数据,队列层用仲裁队列(Quorum Queue)基于 Raft 做多副本,接入层用多节点地址 + 负载均衡让客户端自动重连。注意:经典镜像队列在 3.9 已弃用、4.0 被移除,现在谈 RabbitMQ 高可用就是谈仲裁队列。
一、三层结构
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ node1(disk)│ │ node2(disk)│ │ node3(disk)│
│ 元数据全量 │<──>│ 元数据全量 │<──>│ 元数据全量 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────▼──────────────────▼──────────────────▼──────┐
│ quorum queue "order.pay.q"(3 副本,Raft 多数派) │
│ Leader 在 node1,Follower 在 node2、node3 │
└───────────────────────────────────────────────────┘
▲
┌──────┴──────┐
│ HAProxy/LB │ 客户端配多个地址,故障时切换
└─────────────┘
集群只复制元数据(交换机、队列定义、绑定、用户、策略),消息本体只存在队列所在节点。所以"集群 ≠ 队列高可用",队列副本必须单独配置。
二、两种队列副本机制
| 对比项 | 经典镜像队列(已移除) | 仲裁队列 Quorum Queue |
|---|---|---|
| 复制协议 | 自定义,异步为主 | Raft,多数派提交 |
| 数据安全 | 主节点确认即返回,可能丢 | 多数派落盘才确认,不丢已确认消息 |
| 故障切换 | 需选最同步的镜像,可能丢消息 | Raft 自动选主,秒级 |
| 内存与性能 | 全量镜像占内存 | 磁盘为主,吞吐略低 |
| 状态 | 3.9 弃用,4.0 删除 | 官方推荐,默认类型可配 |
仲裁队列的代价是写延迟:一条消息要复制到多数派才算成功,跨机房部署时延迟会明显上升。因此仲裁队列适合核心业务队列,非核心的高吞吐队列可以用经典队列(非镜像)配合消息补偿。
三、故障场景下的行为
| 故障 | 集群元数据 | 仲裁队列 | 客户端表现 |
|---|---|---|---|
| 单节点宕机 | 不受影响(多数派存活) | 自动选主,秒级恢复 | 连接断开,重连后继续消费 |
| 多数节点宕机 | 只读,无法声明新队列 | 不可用(无多数派) | 发布失败,需人工恢复 |
| 网络分区 | 取决于 partition 策略 | 少数派侧不可写 | 可能出现重复消费 |
| 磁盘写满 | 节点阻塞 | 该节点上的 Leader 迁移 | 生产端收到 nack 或超时 |
网络分区策略在 rabbitmq.conf 里配置:pause_minority 让少数派节点暂停服务,避免脑裂双写,是多数场景的推荐值;autoheal 会在分区恢复后重启少数派节点。
四、集群搭建与队列声明
# 三个节点:node1 为种子
rabbitmqctl -n rabbit@node2 stop_app
rabbitmqctl -n rabbit@node2 reset
rabbitmqctl -n rabbit@node2 join_cluster rabbit@node1
rabbitmqctl -n rabbit@node2 start_app
rabbitmqctl cluster_status
# /etc/rabbitmq/rabbitmq.conf
cluster_partition_handling = pause_minority
default_queue_type = quorum
// 显式声明仲裁队列,并给客户端配置多个节点
Map<String, Object> args = Map.of(
"x-queue-type", "quorum",
"x-quorum-initial-group-size", 3);
ch.queueDeclare("order.pay.q", true, false, false, args);
ConnectionFactory f = new ConnectionFactory();
f.setHost("10.0.0.21,10.0.0.22,10.0.0.23"); // 地址列表,自动选可用节点
f.setAutomaticRecoveryEnabled(true);
五、常见追问
- 为什么仲裁队列要求奇数节点? Raft 的多数派是
n/2+1,3 节点容忍 1 台故障,4 节点仍只容忍 1 台,多一台只增加成本不提升容错。 - 镜像队列为什么被废弃? 同步是异步的、分区后容易脑裂丢消息、全量镜像内存开销大,官方用 Raft 重写后直接移除。
- 集群能跨机房吗? 能但不推荐仲裁队列跨城:多数派提交的延迟会拖慢所有写入,通常用 Shovel/Federation 做异步跨机房复制。
- 节点类型 disk 与 ram 怎么选? RAM 节点只把元数据放内存(消息仍在磁盘),重启后需重新同步;现在推荐全部用 disk 节点,RAM 节点的收益已很小。
小结
- 集群提供元数据高可用,队列副本才提供消息高可用,两者不能互相替代。
- 仲裁队列基于 Raft 多数派提交,是当前唯一推荐的副本方案。
- 客户端必须配多节点地址并开启自动重连,否则节点切换时业务直接中断。
- 网络分区策略与容量规划同样属于高可用的一部分,不能只盯着副本数。