Skip to content

集群基础 ​

RabbitMQ 集群提供高可用和水平扩展能力。真正理解集群,要抓住一个根本区分:元数据是共享的,队列数据不是。这一章围绕这个区分展开。

1. 元数据共享,数据不共享 ​

集群里所有节点共享的是元数据,也就是 exchange、queue、binding 的定义。你在任意节点声明一个 exchange,其他节点立刻知道它存在。

队列里的消息数据不是共享的。一个 Classic Queue 的消息由承载它的那个节点管理,其他节点不会自动保存消息副本。客户端可以连接任意节点访问这个队列;如果连的节点不是队列所在节点,相关操作需要集群内部协调和转发。

txt
Node 1             Node 2             Node 3
└─ Queue A 数据     └─ 无 Queue A 数据  └─ 无 Queue A 数据
(exchange/queue/binding 的元数据各节点都有)

所以 Classic 集群提供的是「统一入口」,不是「数据多副本」。队列操作由承载它的节点处理,承载节点故障时,这个队列可能暂时无法访问。

2. 元数据存储 ​

RabbitMQ 4.3 使用 Khepri 存储集群元数据,Mnesia 已在 4.3 移除。集群元数据的复制和故障恢复由 Khepri 负责。

旧版文档里的 disc / ram 节点类型是 Mnesia 时代的模型,4.x 已不再适用,不要再照搬旧版的 join_cluster --ram 和「至少保留两个 disc 节点」这类说法。

元数据可用性与队列消息数据的可用性是两回事。即使元数据能恢复,没有副本的 Classic Queue 也不会因此多出消息副本。

3. 集群搭建 ​

3.1 Docker Compose ​

yaml
services:
  rabbitmq1:
    image: rabbitmq:4.3-management
    hostname: rabbitmq1
    environment:
      RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG"
    volumes:
      - rabbitmq1_data:/var/lib/rabbitmq

  rabbitmq2:
    image: rabbitmq:4.3-management
    hostname: rabbitmq2
    environment:
      RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG"
    volumes:
      - rabbitmq2_data:/var/lib/rabbitmq

  rabbitmq3:
    image: rabbitmq:4.3-management
    hostname: rabbitmq3
    environment:
      RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG"
    volumes:
      - rabbitmq3_data:/var/lib/rabbitmq

volumes:
  rabbitmq1_data:
  rabbitmq2_data:
  rabbitmq3_data:

这个 Compose 文件只是启动了三个独立的 RabbitMQ 节点。共享 Cookie 是节点间认证的前提,不会自动把三个节点组成集群。完整的部署说明见安装部署,这里只讲把节点连成集群的关键步骤。

3.2 加入集群 ​

bash
# 在 rabbitmq2 上执行,加入 rabbitmq1
rabbitmqctl join_cluster rabbit@rabbitmq1

# 在 rabbitmq3 上执行,加入同一个集群
rabbitmqctl join_cluster rabbit@rabbitmq2

# 在任意节点验证
rabbitmqctl cluster_status

RabbitMQ 4.1 起 join_cluster 会自动完成加入前的准备工作,不再需要先 stop_app、reset、再 start_app。旧教程里那三步是 3.x 的流程,4.x 直接 join 即可。

join_cluster 是破坏性操作。一个节点不能在保留自身数据的同时加入另一个集群,加入时会清掉节点原有数据。所以只对全新或可清空的节点执行,不能对一个已有数据的生产节点直接 join。

集群节点通过 Erlang Cookie 认证。所有节点 cookie 必须一致。在常见 Linux 容器部署中,cookie 位于 /var/lib/rabbitmq/.erlang.cookie,权限 400。

排查连接失败时要分清两类错误。unable to connect to epmd 指向节点发现或网络问题,先查主机名解析、4369 端口连通性和节点是否在运行。authentication failed 才指向 Erlang 分布认证失败,此时优先核对 cookie 是否一致。不要把所有连接失败都归因到 cookie 上。

5. 集群端口 ​

端口用途
5672AMQP 协议
15672Management UI
4369EPMD 节点发现
25672节点间 Erlang 分布通信

这些是默认端口,实际值受配置影响,启用 TLS 或改监听配置后会不同。

4369 是 EPMD 做节点发现用,25672 承担节点间通信。防火墙漏开这两个端口,节点可能直接无法加入集群,也可能在运行期出现节点间通信故障,而不是「能加入但只是状态同步异常」。排查时分别检查端口连通性、节点名解析和认证配置。

6. 数据分布 Classic vs Quorum ​

Classic Queue 的数据在单节点,Quorum Queue 通过 Raft 复制到多个节点。

维度Classic QueueQuorum Queue
数据副本单份多份(Raft)
读从所属节点从 Leader
所属节点故障队列暂时不可用多数成员可用时选举新 Leader,恢复服务

旧版 Classic 通过镜像队列提供复制和故障转移能力,镜像队列自 3.9 弃用、4.0 已移除,见镜像队列。

选型不能简单说「需要数据不丢就用 Quorum」。Quorum Queue 的数据安全是有条件的:已收到 Publisher Confirm 的消息,在多数副本安全时不会丢;尚未确认的在途消息不享有同等保证。Classic 队列所在节点故障时,队列只是暂时不可用,节点恢复且数据完好就能继续服务;只有数据永久丢失才无法恢复。

性能上,Classic 少了多副本复制和共识的开销,但实际吞吐受消息大小、持久化、确认方式和消费模式影响,不能说它一定「极致吞吐」。需要数据跨节点复制和节点故障容忍时优先 Quorum,能接受单节点故障导致队列短暂不可用、且经过压测确认的,再用 Classic 单节点。

7. 一次节点故障演练 ​

假设三节点集群正常运行,Queue A 是包含三个成员的 Quorum Queue,Queue B 是位于 Node 1 的 Classic Queue。现在 Node 1 突然宕机,Node 2 和 Node 3 仍正常运行。

元数据层面,Khepri 需要多数节点才能提交元数据变更。Node 1 故障后,剩余两个节点仍构成多数派,元数据变更可以继续提交;如果剩余节点不足多数,元数据变更会无法提交。所以不能凭 exchange 和 binding 的定义还在,就断定所有发布操作不受影响。

Queue A(Quorum),Node 2 和 Node 3 上的成员仍构成多数派,可以选出新 Leader。选举期间队列操作会短暂中断,新 Leader 就绪后恢复服务。已经收到 Publisher Confirm 的消息在多数副本安全条件下不会丢,尚未确认的在途消息不在此保证内。

Queue B(Classic),数据只在 Node 1,节点宕机后这个队列不可用,消费者连不上,消息取不到。Node 1 恢复且本地数据完好时,队列随节点恢复;如果 Node 1 的数据永久丢失,集群其他节点无法恢复这些消息。

这个场景说明,元数据共享不能替代队列内容复制。需要承受节点故障时,按队列复制方式、消息确认策略和故障恢复目标来选型,而不是一律优先 Quorum。

8. 节点故障处理 ​

bash
# 计划下线:在要下线的节点上执行,停止 RabbitMQ 应用
rabbitmqctl stop_app

# 移除故障节点:在仍可用的集群节点上执行
rabbitmqctl forget_cluster_node rabbit@failed_node

这两条命令用于不同场景,不是必须连续执行的步骤。stop_app 在要下线的节点上执行,只停应用不停 Erlang 运行时。forget_cluster_node 要在仍可用的集群节点上执行,把故障节点从成员关系里移除。

forget_cluster_node 只在确认节点不会回来时执行。它修改的是成员关系,不等于删除该节点上的数据。被移除的节点如果之后重新上线,不会自动回到集群,需要根据节点本地状态决定重新加入还是先重置,不能想当然地认为它会变回集群成员。

移除节点也不等于恢复它的队列消息。故障节点上的 Classic Queue 若唯一副本已永久丢失,其他节点无法靠成员关系操作找回这些消息。

Released under the MIT License.