RabbitMQ 是如何保证高可用的?

#中间件#面试八股

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 多数派提交,是当前唯一推荐的副本方案。
  • 客户端必须配多节点地址并开启自动重连,否则节点切换时业务直接中断。
  • 网络分区策略与容量规划同样属于高可用的一部分,不能只盯着副本数。
笔记加载中…