4.3 集群与高可用:拆掉单点故障


文档摘要

4.3 集群与高可用:拆掉单点故障 本节摘要:集群把多台 Broker 连成逻辑整体,元数据在节点间同步,但队列消息默认仍只存在一个节点——不配副本策略的集群挡不住数据丢失。本节讲清集群的同步模型、经典镜像与仲裁队列两种副本方案的取舍,并完成一次两节点集群的搭建与故障演练。 改造第三步,也是最贵的一步。前两章为消息可靠做的全部努力,都建立在"这台机器活着"的前提上——4.1 节的持久化能扛住进程重启,扛不住机器彻底损坏。集群与副本,就是把"机器必须活着"这个假设拆掉。 集群同步模型:先分清"什么同步了" RabbitMQ 集群中,所有节点共享一份元数据:交换机、队列的定义、绑定、用户与 vhost,在任何节点上声明,全集群可见。这份同步让客户端连任何一个节点都看到同一个"路由世界"。

4.3 集群与高可用:拆掉单点故障

本节摘要:集群把多台 Broker 连成逻辑整体,元数据在节点间同步,但队列消息默认仍只存在一个节点——不配副本策略的集群挡不住数据丢失。本节讲清集群的同步模型、经典镜像与仲裁队列两种副本方案的取舍,并完成一次两节点集群的搭建与故障演练。

改造第三步,也是最贵的一步。前两章为消息可靠做的全部努力,都建立在"这台机器活着"的前提上——4.1 节的持久化能扛住进程重启,扛不住机器彻底损坏。集群与副本,就是把"机器必须活着"这个假设拆掉。

集群同步模型:先分清"什么同步了"

RabbitMQ 集群中,所有节点共享一份元数据:交换机、队列的定义、绑定、用户与 vhost,在任何节点上声明,全集群可见。这份同步让客户端连任何一个节点都看到同一个"路由世界"。

但队列的消息内容是另一回事。普通队列的数据只驻留在它"所属"的那个节点——其他节点上只能看到它的定义,客户端连到别的节点访问该队列时,数据会被内部转发。于是出现本节开头必须拆穿的那个误读:集群本身只提供可用性路由,不提供数据冗余。所属节点宕机,它的非镜像队列直接消失,消息随之丢失——集群越组越多,单点却一个没少,只是更贵了。

给队列配副本才是数据安全的正解。3.x 提供两条路线:

维度 经典镜像队列 仲裁队列
一致性模型 主从异步复制,极端情况可丢尾部 Raft 多数派写入,强一致
故障切换 主镜像选举,秒级 自动选主,秒级
性能特征 吞吐高,写放大随副本数增加 单写吞吐略低,语义清晰
官方态度 已不推荐用于新系统 当前推荐方案

新系统优先仲裁队列:语义干净(多数派写入才算成功)、故障切换自动、与发送方确认机制天然契合。老系统里已大规模使用镜像队列的,按官方路线图规划迁移即可,两者不宜长期混用。

副本在集群里的分布方式,用一张拓扑图钉死:

图 12 三节点集群与队列副本分布

图 16 三节点集群与队列副本分布

读图的要点在两排色块:绿色是配了三副本的仲裁队列,三个节点各有数据,宕一台自动接管;橙色是没配副本的普通队列,数据只躺在一个节点上,另外两个节点上只看得到"队列应该存在"的元数据。集群给拓扑高可用,副本才给数据高可用——这一格区分开了形形色色的"伪高可用"方案。

完整演练:两节点集群搭建与宕机演练

背景:两台机器 node1 与 node2,从零组成集群,给订单队列配三副本(两节点场景实为两副本),并做一次真实的宕机演练。

操作:第一步,两台机器的 Erlang Cookie 必须一致(集群节点互信的根基),然后 node2 加入 node1:

# node2 上执行:同步 cookie 后停应用再加入集群 rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster rabbit@node1 rabbitmqctl start_app # 回到 node1 验证集群状态 rabbitmqctl cluster_status # 预期输出(节选): # Running Nodes # rabbit@node1 rabbit@node2

第二步,声明仲裁队列并注入消息:

import pika connection = pika.BlockingConnection( pika.ConnectionParameters(host="node1")) channel = connection.channel() # 仲裁队列:x-quorum-initial-group-size 指定初始副本数 channel.queue_declare( queue="order.quorum", durable=True, arguments={"x-queue-type": "quorum"}) channel.confirm_delivery() channel.basic_publish( exchange="trace.direct", routing_key="order.created", body=b"cluster-safe message", properties=pika.BasicProperties(delivery_mode=2)) print("消息已按多数派规则写入")

第三步,宕机演练:直接关停 node1,观察客户端重连与消息完好性:

systemctl stop rabbitmq-server # 在 node1 上执行 # 客户端改连 node2 后清点 rabbitmqctl list_queues name messages # 预期输出: # order.quorum 1

结果:node1 宕机后,node2 接管仲裁队列,消息一条不少;客户端用重连逻辑换节点即可恢复业务。解读:演练暴露了一个必答的工程问题——客户端怎么知道该连谁?答案是负载均衡层:连接地址配成 VIP、DNS 轮询或负载均衡器,宕机时客户端重连自动落到存活节点。另外注意两副本集群的多数派含义:一节点宕机时剩余单节点仍满足多数派(2 取 2 分之 1 加 1 需两票……严格说两节点组只能容忍零节点故障后仍可写),仲裁队列建议三副本起步,两节点组只能容忍一台宕机时的读取,写入需谨慎评估——本演练作教学演示,生产按三节点起步。

变式一:把队列改回普通队列(不带 x-queue-type),重复宕机演练,观察 node2 上队列消失——亲手验证"集群不等于高可用"。变式二:node1 恢复后 rejoin 集群,观察副本自动补齐,期间消息不重不丢——仲裁队列的恢复是自动的,无需人工干预。

集群运维三条纪律

纪律一,跨机房慎组集群。集群依赖低延迟的节点间通信,跨机房网络抖动会放大成集群震荡,甚至出现脑裂。跨机房容灾用 Federation 或 Shovel 插件做异步复制(5.2 节插件篇展开),不要硬组一个跨城集群。纪律二,硬件对齐。集群节点保持同版本、同 Erlang 版本、相近规格——版本漂移是滚动升级事故的头号来源。纪律三,演练常态化。宕机演练不是一次性的:每季度拔一次节点,验证副本补齐、客户端重连、告警触发三件事都真的有效。

⚠️ 常见坑:以为仲裁队列不需要持久化标记。仲裁队列的多数派写入本身保证副本冗余,delivery_mode 语义在其中弱化,但发送方确认依然必须开启——"写入成功"要以 Broker 回执为准,客户端的乐观假设在分布式系统里一文不值。

本节要点回顾

  • 同步分两层:元数据全集群同步,消息内容默认单节点,副本策略补后者;
  • 副本选型:新系统用仲裁队列(Raft 强一致),镜像队列只余存量维护;
  • 搭建三步:同步 Cookie、加入集群、配副本队列,cluster_status 验收;
  • 客户端接入:VIP 或负载均衡加重连逻辑,宕机切换对业务透明;
  • 三条纪律:不跨机房硬组、版本对齐、宕机演练常态化。

单点拆掉了。最后一站给集群装上眼睛:监控指标、告警规则与日志排障——让故障在用户感知之前先被看见。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U