前两篇讲了经典队列的存储机制,但故意回避了一个问题:经典队列是单机的,节点挂了怎么办? RabbitMQ 历史上给出的答案是镜像队列(Mirrored Queue)——把队列在多个节点间镜像复制,一个挂了另一个顶上。这套方案用了很久,但在 RabbitMQ 4.0 被彻底移除了。

理解镜像队列为什么被废弃,比记住「现在用 Quorum Queue」重要得多。因为它揭示了一个普遍的分布式问题:主从复制模型在网络分区下是不可靠的。这个教训不只适用于 RabbitMQ,MongoDB 的主从、Redis 的旧版主从、MySQL 的异步复制,都踩过同样的坑。RabbitMQ 只是第一个公开承认这个模型不可行、并彻底重做的主流中间件。

镜像队列的工作方式

镜像队列的核心是主从复制

  • 一个队列在多个节点上有副本,其中一个是 master(主),其余是 mirror(镜像/从)
  • 生产者发消息,master 先接收并写入,然后异步复制给所有 mirror。
  • 消费者只从 master 消费(mirror 不直接服务消费)。
  • master 挂了,某个 mirror 被提升为新的 master。

这套模型看起来合理:异步复制不影响 master 的写入吞吐,多副本提供容错。问题藏在这些特性背后的细节里。

异步复制的丢失窗口

镜像队列的复制是异步的——master 写入成功就返回 ack 给生产者,复制给 mirror 是后台进行的。这意味着存在一个丢失窗口:master 写入成功,还没来得及复制给 mirror 就崩溃了,这条消息在 master 恢复(或变成 mirror)之前是丢的。

生产者收到了 ack(以为消息安全了),但消息其实只存在于已经崩溃的 master 上。这种「确认了却丢失」是最隐蔽的不可靠——比直接拒绝更糟糕,因为它让人误以为没问题。

要消除这个窗口,必须用同步复制——master 等所有 mirror 确认才算写入成功。但同步复制会让吞吐大幅下降(每条消息都要等最慢的 mirror),而且 mirror 慢或挂了会拖累 master。镜像队列的设计在「吞吐」和「可靠」之间选择了吞吐,代价就是这个丢失窗口。

网络分区下的脑裂

比丢失更严重的是脑裂(split-brain)。网络分区发生时,集群被分成两半,彼此以为对方挂了:

  • 每一半都可能各自提升出一个 master(因为它们都觉得自己是「多数」)。
  • 两个 master 同时接收写入,各自复制给自己的 mirror。
  • 分区恢复时,两个 master 的数据不一致,必须丢弃一方——被丢弃的那部分写入永久丢失。

镜像队列对脑裂的处理很被动。它有几种分区处理策略(pause-minority、autoheal 等),但每种都有代价:

  • pause-minority:少数派节点自我暂停,避免双 master。代价是分区时少数派完全不可用。
  • autoheal:分区恢复时按某种规则决定保留哪个 master,丢弃另一个。代价是明确丢数据。

没有一种策略能既不丢数据又不影响可用性。这是主从复制模型在网络分区下的根本困境——它没有一个权威机制来判定「谁是真正的多数」。

GM 协议的复杂度

为了缓解这些问题,镜像队列内部实现了一套叫 GM(Guaranteed Multicast) 的协议,试图保证消息在所有 mirror 上的一致性。但 GM 协议极其复杂:

  • 它本质上是一个全组的可靠广播协议,要处理节点加入、退出、故障。
  • 实现代码难以维护,bug 难以排查。
  • 性能随镜像数量增加而下降(每个镜像都要参与协议)。

这套复杂协议试图在「异步复制」的框架内硬补一致性,结果是用高复杂度换来一个依然不完美的一致性保证。这是典型的「在错误的模型上打补丁」——补丁越打越厚,问题没根治。

为什么 Raft 能解决这些问题

RabbitMQ 团队最终意识到,主从复制模型本身是问题根源,必须在模型层面换掉。他们选择了 Raft 共识算法,这就是 Quorum Queue 的基础(下一篇详谈)。Raft 解决了主从复制的三个根本缺陷:

  • 强一致性:写入必须经多数节点确认才算成功,不存在「确认了却丢失」的窗口。
  • 明确的领导者选举:Raft 用 term(任期)和投票机制保证任何时刻最多一个 leader,网络分区时少数派无法选出 leader,从根本上避免脑裂。
  • 成熟的理论保证:Raft 是被证明的共识算法,不像 GM 那样是自研的工程妥协。

Raft 的代价是:写入延迟更高(要等多数确认)、协议本身有开销。但换来的是真正的一致性和容错——这笔交易对「不能丢消息」的核心业务是划算的。

一个更普遍的教训

镜像队列的废弃,反映了一个超越 RabbitMQ 的工程教训:异步主从复制在网络分区下是不可靠的,要做强一致,必须用共识算法(Raft/Paxos)

这个教训在很多系统里都验证过:

  • MongoDB 早期用主从复制,后来转向 Replica Set(基于 Raft 变种)。
  • Redis 主从复制至今仍是异步的,所以 Sentinel/Cluster 在分区时也面临数据丢失风险(Redis 的定位是缓存,容忍这个)。
  • etcd、Consul 从一开始就用 Raft,因为它们的元数据不能错。

RabbitMQ 选择用 Raft 重做队列复制(Quorum Queue)和元数据存储(Khepri),是把整个系统的「可靠性根基」从异步复制迁到共识算法。这是一次彻底的架构转向,也是 4.0 移除镜像队列的根本原因。

迁移:从镜像队列到 Quorum

RabbitMQ 4.0 移除镜像队列,意味着所有还在用镜像队列的用户必须迁移。迁移路径:

  • 新队列直接用 Quorum Queue 声明。
  • 存量镜像队列,在升级到 4.0 时会提示迁移。官方提供了迁移工具和指引。
  • 迁移期间可以两者并存(3.x 支持镜像和 Quorum 共存),逐步把镜像队列消费完、切到 Quorum。

迁移要注意 Quorum 和镜像的特性差异:Quorum 不支持某些镜像队列有的特性(如消息优先级在某些版本受限),迁移前要核对特性矩阵。

主从复制到 Raft 共识的演进

收束:模型决定可靠性上限

镜像队列的故事是一个关于「模型」的教训。它的失败不在实现层面,而在模型层面——异步主从复制在分布式环境下无法保证一致性和容错。再精巧的协议(GM)也无法弥补模型的缺陷。

下一篇讲 Quorum Queue 怎么用 Raft 彻底重做队列复制,那是 RabbitMQ 在存储可靠性上的「正确答案」。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。我相信 AI 是程序员的最佳搭档。想跟完这套「图解 RabbitMQ」,欢迎关注公众号 「十三Tech」

十三Tech公众号二维码