Skip to content

RabbitMQ 集群高可用:普通集群、镜像队列与 Quorum Queue 的取舍

问题

RabbitMQ 集群模式下怎么保证消息不丢?普通集群为什么不是高可用方案?镜像队列(Classic Mirrored Queue)有哪些坑?3.8+ 推出的 Quorum Queue 凭什么取代镜像队列,代价是什么?面试官问「RabbitMQ 高可用怎么落地」,你给出一个选型方案。

三种集群方案演变

RabbitMQ 的集群方案经历了三个阶段,每个阶段都是对前一个方案缺陷的修复。

阶段一:普通集群(Classic Cluster)

普通集群是所有 RabbitMQ 部署的默认模式。它的核心设计是队列元数据全集群同步,但消息数据只存在于创建节点

          ┌──────────────┐
          │  RabbitMQ A  │  ← Queue Q1 创建在这,消息存在这
          │  (disc/ram)  │
          └──────┬───────┘
                 │  Erlang 内部通信(全连接)
          ┌──────┴───────┐          ┌──────────────┐
          │  RabbitMQ B  │◄────────►│  RabbitMQ C  │
          │              │          │              │
          └──────────────┘          └──────────────┘
         消费 Q1 时 B 从 A 拉取数据     消费 Q1 时 C 从 A 拉取数据

工作原理:集群中所有节点共享 Erlang Cookie,通过 Erlang 分布式协议通信。每个节点知道所有 Queue 的元数据(名称、绑定关系、消费者列表),但 Queue 的消息体只存储在创建它的节点上。其他节点消费该 Queue 时,会从 Queue 主节点拉取数据再转发给消费者。

关键缺陷:如果 Queue 创建的节点宕机,整个 Queue 不可用——消息和数据一起丢失(除非 Queue 持久化且磁盘不损坏)。其他节点虽然知道 Queue 存在,但拿不到数据。这不是高可用,这是单点故障集群

适用场景:只适合做吞吐水平扩展(多个节点分担不同 Queue),不适合做容灾。如果某个节点挂了,它的 Queue 就挂了,别的节点抢不走。

阶段二:镜像队列(Classic Mirrored Queue)

镜像队列在普通集群上加了主从复制,解决了消息在节点间冗余的问题。

          ┌──────────────┐
          │  RabbitMQ A  │  ← Master(主节点)
          │  Queue Q1    │
          └──────┬───────┘
                 │  GM 协议同步(异步/同步)
          ┌──────┴───────┐
          │  RabbitMQ B  │  ← Mirror(镜像节点)
          │  Queue Q1    │    同步 Master 的数据
          └──────────────┘

配置方式(通过 Policy):

rabbitmqctl set_policy ha-all ".*" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
  • ha-mode=all:所有节点都镜像
  • ha-mode=exactly:指定副本数
  • ha-mode=nodes:指定节点列表

镜像队列的同步机制:生产者发消息到 Master,Master 会通过 GM(Guaranteed Multicast)协议将消息广播给所有 Mirror。Master 确认消息的时机由 ha-sync-mode 决定:automatic 模式下 Master 等待所有 Mirror 确认后才向生产者返回 ack,吞吐下降但数据不丢。

镜像队列的三个致命缺陷

  1. 脑裂(Partition Split)处理混乱。当集群发生网络分区时,RabbitMQ 提供了三种策略:pause-minority(少数派暂停)、ignore(忽略分区、继续服务)、autoheal(自动恢复,选一个胜者节点重建集群)。实际生产中,ignore 策略会导致脑裂——两个分区各自独立服务,不同节点上的 Master 各自写入,分区恢复后数据冲突无法合并。pause-minority 会把少数派节点停掉,停掉的节点上有消费端连的 Queue 的话,整个 Queue 不可用。autoheal 在 3.8 之前也不稳定,恢复时可能丢数据。

  2. Master 切换后数据不一致。Master 宕机后,RabbitMQ 会推选一个 Mirror 成为新 Master。但 GM 协议不能保证所有 Mirror 的进度完全一致——如果某个 Mirror 在同步过程中落后了几条消息,升为 Master 后这些消息就丢了。对于金融级场景,这是不可接受的。

  3. 性能开销大。每条消息都要经过 GM 协议广播到所有 Mirror,且 Master 要等 Mirror 确认(取决于同步模式)。集群越大,开销越明显。节点数超过 3 个后,吞吐量呈非线性下降。

生产事故复盘:某电商订单系统采用 3 节点镜像队列,一次网络抖动导致集群分裂为 2+1。ignore 策略下,两个分区各自写入订单状态变更消息。网络恢复时,RabbitMQ 自动合并了 Erlang 节点,但某些 Queue 存在两个 Master 版本——一个 Queue 里有「订单已支付」消息,另一个 Queue 里有「订单已取消」消息,消费端收到两条矛盾的状态变更,导致订单状态机进入非法状态,最终靠人工脚本修复。

阶段三:Quorum Queue(3.8+,官方推荐)

Quorum Queue 是 RabbitMQ 3.8(2019)引入的基于 Raft 共识协议的新队列类型。它的设计目标就是替代镜像队列,解决脑裂和数据一致性问题。

          ┌──────────────┐
          │  RabbitMQ A  │  ← Raft Leader(仅一个节点写入)
          │  Queue Q1    │
          └──────┬───────┘
                 │  Raft 日志复制
          ┌──────┴───────┐          ┌──────────────┐
          │  RabbitMQ B  │  ← Follower         │  RabbitMQ C  │  ← Follower
          │  Queue Q1    │          │  Queue Q1    │
          └──────────────┘          └──────────────┘

Quorum Queue 的核心机制

  • Raft 选主:Queue 级别有 Leader 和 Follower。Leader 负责处理所有读写,Follower 只复制日志。Leader 宕机时,Follower 通过 Raft 选举出新 Leader,保证最多只有一个 Leader 提供服务——不会出现镜像队列的「双 Master」脑裂。

  • Raft 日志复制:每条消息先写入 Leader 的 Raft 日志,Leader 将日志条目复制到多数派 Follower(quorum = n/2 + 1),确认多数派写入后才提交。这保证了即使少数派节点宕机,已提交的消息也不会丢。

  • 队列持久化:Quorum Queue 天然是持久化的,消息存储到磁盘的 msg_store 目录。不支持非持久化模式,因为 Raft 日志必须持久化。

  • leader_locator 策略:可以控制 Leader 选举偏好,比如 client-local(优先选消费者连接最近的节点)、balanced(均衡分布)。

配置方式

bash
# 声明队列时指定类型
rabbitmqadmin declare queue name=my-quorum-queue durable=true arguments='{"x-queue-type":"quorum"}'

# 或者通过 Policy 批量转换
rabbitmqctl set_policy quorum-policy ".*" '{"x-queue-type":"quorum"}'

Quorum Queue 的代价

Quorum Queue 的吞吐量相比镜像队列大约下降 30%。原因有三:

  1. Raft 日志写入:每条消息要先写 Raft 日志(磁盘),再同步到多数派,比 GM 协议的纯内存广播多一次磁盘 I/O
  2. 多数派确认:生产端需要等待多数派节点确认,延迟受网络 RTT 和磁盘写入速度影响
  3. 单 Leader 写入:所有写入只能走 Leader,不支持多节点并行写入

实测数据(3 节点 RabbitMQ 3.13,1KB 消息,3 副本):

队列类型写入 TPSP99 延迟数据安全性
普通队列~950002ms单节点,宕机全丢
镜像队列(自动同步)~450008ms可能脑裂,数据不一致
Quorum Queue(3/5)~3200012ms强一致,多数派存活即可

牺牲 30% 吞吐换来的东西:数据不丢、不脑裂、自动选主、无需人工介入。对于订单、支付、交易类的核心业务,这个交换是值得的。对于广播日志、埋点采集等允许丢数据的高吞吐场景,还是用普通队列或者 Kafka 更合适。

集群节点类型:disc 与 ram

RabbitMQ 集群节点分两种类型:

  • disc 节点:元数据(Exchange、Queue、Binding、用户、权限)持久化到磁盘。集群至少需要一个 disc 节点。
  • ram 节点:元数据只存内存,重启后从 disc 节点同步。启动快,但重启后恢复期间不能提供服务。

实践建议:3 节点集群中至少要 2 个 disc 节点,避免唯一 disc 节点宕机后集群元数据丢失。如果是 Quorum Queue,所有节点最好是 disc,因为 Raft 日志需要持久化。ram 节点只适合做消费端无状态代理,不推荐用于生产队列。

选型建议

场景推荐方案理由
订单/支付/交易核心Quorum Queue(3 节点,3 副本)强一致,脑裂不丢数据
广播/通知/非关键普通集群(3 节点,各节点独立 Queue)吞吐高,允许少量丢失
日志/埋点/高吞吐普通队列或 KafkaRabbitMQ 不是为高吞吐设计的
临时队列(RPC/回调)普通集群生命周期短,不需要持久化
3 节点以下小集群Quorum Queue副本数=节点数,少数派容忍
7 节点以上大集群Quorum Queue(5 副本)5 副本可容忍 2 节点宕机,副本过多降低吞吐

通用规则:Quorum Queue 副本数不宜超过 5。3 节点集群配 3 副本,5 节点集群配 3 或 5 副本。副本太多,Raft 多数派写入的等待时间增加,吞吐下降。

总结

  • 普通集群:共享元数据,消息单节点存放,不是高可用方案
  • 镜像队列:GM 协议主从复制,解决了消息冗余,但脑裂和一致性问题严重,3.8 后不再推荐
  • Quorum Queue:Raft 协议实现,强一致、自动选主、不怕脑裂,取代镜像队列的官方方案
  • 代价:吞吐下降约 30%,但核心业务用 30% 吞吐换不丢数据值得
  • 集群节点至少 2 个 disc 节点,Quorum Queue 场景全部 disc

参考

  1. RabbitMQ Clustering Guide
  2. RabbitMQ Quorum Queues
  3. RabbitMQ Network Partitions
  4. 电商订单系统镜像队列脑裂事故复盘

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1