先说个我自己的真实经历。上家公司有个订单服务,某个周二中午高峰,突然大面积超时报警。排查下来发现是 RabbitMQ 那台机器磁盘写满,队列消息全堵住,下游服务拿不到数据,业务一路连环报错。好在当时核心队列已经做了高可用改造,消息在另外两个节点上都有镜像副本,切主之后十几分钟恢复,没酿成全站事故。从那以后我就认死一个理:消息队列平时越安静,挂掉的时候破坏力越大,高可用这件事不是可选项,是生产环境的必选项。
这篇内容围绕 RabbitMQ 的高可用队列展开,重点讲镜像队列的原理、集群搭建、策略配置、客户端接入,以及我在生产环境踩过的坑。适合刚接触消息队列的后端开发、运维同学,也适合已经在用 RabbitMQ 但还没认真规划高可用的团队参考。
1. 为什么消息队列一定要做高可用
1.1 消息队列的三大作用,失效时有多痛
老生常谈,消息队列的三大作用是解耦、异步、削峰。但你得先想清楚一个事:这三个作用带给系统好处的同事,也把系统的一部分命脉交到了消息队列手上。
- 解耦:订单服务不需要同步调用积分、短信、库存、搜索那一堆下游,只要把“订单创建完成”这个事件发到队列里就行。代价是队列变成核心依赖,它挂了,所有服务间的异步通信全断。
- 异步:请求发完消息就返回,用户感知很快。代价是如果队列不可用,请求要么卡住,要么被迫改回同步流程,系统瞬间退化。
- 削峰:大促流量先进队列,下游按自己的速率慢慢消费。代价是队列一旦出问题,削峰变成了“堵峰”,后面所有业务全被堵住。
我习惯把消息队列类比成快递柜。寄件人放下包裹就走,取件人自己有空来拿,双方不用碰面,这就是解耦和异步;快递柜能同时收几百个件,取件人一个个拿,这就是削峰。但快递柜本身要是被人砸了或者门锁坏了,寄件人东西放不下只能原地等着,取件人取不到件开始投诉,整个流程直接瘫痪。消息队列高可用要解决的就是这个“快递柜被砸了怎么办”的问题。
1.2 高可用到底要解决什么问题
很多团队对消息队列高可用的理解停留在“多部署几台机器”,这个认知是可以的,但不完整。展开说,高可用要覆盖三层:
第一层,服务可用性。某个节点挂掉之后,消息还能不能被正常生产和消费。比如一个节点宕机,消费者不能因为这个节点挂了就连不上队列,必须能自动切换到其他节点继续工作。
第二层,数据可靠性。节点挂掉之后,已经写入的消息会不会丢。这个比第一层更隐蔽。有些方案服务是能切了,但切过去之后发现消息少了一大截,这种高可用是有问题的。
第三层,自动恢复。故障之后,系统能不能自动把缺失的副本补回来,能不能在节点重新加入后自动恢复同步。如果每次故障都要人工介入去同步数据,高可用的价值就大打折扣。
把这三层拆开之后你再去看 RabbitMQ 的各种配置,思路会清晰很多。比如持久化解决的是数据可靠性,镜像队列解决的是副本层面的可用性和可靠性,客户端自动恢复解决的是服务可用性,这些都不是同一层面的东西,不能混为一谈。
1.3 架构选型:普通集群、镜像队列与仲裁队列
RabbitMQ 的集群方案有好几种,聊高可用之前必须先分清它们的定位。
普通集群(Classic Cluster)解决的是吞吐和节点级容灾的问题。集群里所有节点共享交换器、队列的元数据,也就是说你在任何一个节点都能看到所有队列的名字和绑定关系。但注意,队列里的消息数据本身只存在一个节点上。如果持有消息数据的那个节点挂了,消息就不可见了,除非节点恢复。类比一下就是:办公室里所有人都知道“文档在3号柜”,但文档原件只有一份放在3号柜,3号柜烧了,大家只知道文档在哪,文档本身却没了。
镜像队列(Mirrored Queue)就是为了解决这个问题被引入的。它把队列的数据复制到多个节点,一个 master 带若干个 mirror,消息写入 master 后会同步到 mirror。master 挂了,从 mirror 里晋升一个新的 master,消息数据不会丢。这相当于同一个文档每个人都有一份完整复印件,经理手里是正本,经理请假了就从其他人里挑一个顶上。
3.8 版本之后,RabbitMQ 又引入了仲裁队列(Quorum Queue),基于 Raft 共识协议实现,比镜像队列有更好的一致性保证,也是官方未来主推的方向。但从存量系统的实际情况看,镜像队列目前仍然大量存在,很多团队的历史集群都是镜像队列架构。所以不管你是要维护老项目,还是准备做技术升级,镜像队列都是必须掌握的核心技能。后面我会重点讲镜像队列,仲裁队列的情况在对比表格里带一笔。
| 方案 | 数据副本 | 主节点故障处理 | 一致性 | 适用阶段 |
|---|---|---|---|---|
| 普通集群 | 仅元数据多节点,消息单节点 | 持有消息的节点故障则消息不可消费 | 弱 | 开发、测试环境 |
| 镜像队列 | master + mirror 多副本 | 自动从 mirror 晋升 | 存在未同步消息可能丢失 | 存量生产环境主流 |
| 仲裁队列 | Raft 多副本 | 自动选举 | 强一致,协议保证 | 新项目推荐 |
2. 镜像队列的工作原理与核心参数
2.1 镜像队列是怎么运转的
镜像队列在 RabbitMQ 里的实现思路是:一个队列逻辑上只有一个,物理上在多个节点上各保留一份数据。其中一个是主节点,官方叫 master,剩下的都是从节点,叫 mirror。
所有读写请求都走 master。生产者发消息,先写到 master,然后由 master 把变更同步给各个 mirror。消费者消费消息,也是先读 master,消费完 ack 之后,master 再把“这条消息已 ack”的标记同步给 mirror。mirror 平时不参与读写,它的存在价值就是当 master 出问题时顶上去。
master 挂了之后,RabbitMQ 会从所有 mirror 里挑一个晋升为新的 master。关键策略是:优先选择与旧 master 同步时间最长的那个 mirror,因为它的数据最接近旧 master。所以 raft 里常说“多数派”,镜像队列这里不严格要求多数派,而是谁数据新谁上。
这个逻辑用生活场景理解就是:公司里一份项目文档,经理手里是正本,几个同事手里各有一份复印件。经理每天改完都会把改动同步给大家。某天经理突然离职,公司要从同事里选一个当新经理,选谁?肯定选“最近一次同步里拿到最新版本”的那个人。如果选了一个手里版本落后很多的同事,那后面开会就会丢掉很多信息。镜像是同样的道理。
2.2 核心参数:ha-mode、ha-params、ha-sync-mode 逐个说清
镜像队列的参数通过 Policy(策略)来配置。Policy 本质上是一组规则,用正则匹配队列名,匹配到的队列就应用对应的镜像参数。最常见的参数有这几个:
| 参数 | 取值 | 说明 |
|---|---|---|
| ha-mode | all / exactly / nodes | 镜像范围。all 表示所有节点都放副本;exactly 表示指定副本数量;nodes 表示指定节点列表 |
| ha-params | 取决于 ha-mode | ha-mode=exactly 时填数字,比如 2;ha-mode=nodes 时填节点列表 |
| ha-sync-mode | automatic / manual | 新镜像节点是否自动同步数据,默认 manual |
| ha-sync-batch-size | 数字 | 同步时每批传输的消息数量,默认较小,调大能加快同步但会增加网络压力 |
| ha-promote-on-shutdown | always / when-synced | master 正常关闭时是否晋升 mirror 为新 master |
| ha-promote-on-failure | always / when-synced | master 异常故障时是否晋升 mirror |
配置命令长这样:
rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all","ha-sync-mode":"manual","ha-promote-on-failure":"when-synced"}'这条命令的意思是:匹配所有队列名,给它们配置 all 模式的镜像,每个节点都保留一份副本,同步模式设为手动,master 故障时只在有数据同步完成的 mirror 存在的情况下才晋升。关于 why:
- ha-mode=all 配置最简单,适合节点数量少、队列总量可控的集群。但如果集群几十个节点,全镜像会导致每一条消息在所有节点上都写一份,浪费巨大。
- ha-sync-mode=manual 是我强烈建议生产环境使用的设置。automatic 看起来省事,但新节点加入时会自动触发全量同步,如果队列里积压了几百万条消息,同步过程会把 CPU、磁盘 IO 和网络全打满,队列还会被阻塞,影响线上业务。
管理界面也能配 Policy。登录 15672 管理端口,找到 Admin -> Policies,Add 一条策略,填入 Name、Pattern(正则)、Priority、Definition(就是上面那组 JSON 参数),保存即可。注意多个 Policy 匹配同一个队列时,RabbitMQ 会按优先级高的优先,优先级相同则按最精确匹配的规则处理,所以命名和优先级要提前规划好,不然配了镜像不生效都不知道。
2.3 同步机制与数据安全性:容易忽略的坑
镜像队列的一个核心概念是 synchronised mirror(已同步镜像)。新加入的 mirror 一开始不是同步状态的,它需要先从 master 把全量数据拉过来,同步完成之后才进入 synchronized 状态。只有处于 synchronized 状态的 mirror,才能参与 master 故障时的晋升选举。
这就引出一个非常关键的取舍:如果 master 出故障时,所有 mirror 都还没有完全同步,那么无论晋升谁,都会丢掉一部分消息。ha-promote-on-failure 这个参数就是控制这个场景的:
- always:只要还有 mirror,不管它数据新不新,都晋升为新 master。好处是队列持续可用,坏处是可能丢消息。
- when-synced:只有存在已同步完成的 mirror 时才晋升。好处是尽量不丢消息,坏处是如果所有 mirror 都还没同步完成,队列会暂时不可用。
我举一个实际场景你就会明白怎么选。订单核心链路的消息,我建议 when-synced,宁可短暂不可用也不能丢单;日志采集或者非关键通知类的消息,用 always 更合适,丢几条无所谓,但服务不能停。这个选择没有绝对正确,完全取决于业务对数据丢失的容忍度。
另一个容易忽略的问题:镜像复制解决的是副本问题,不代表消息持久化。如果你创建队列时没有设置 durable 属性,消息只写在内存里,节点重启就没了。镜像队列的高可用必须和持久化队列、持久化消息配合使用。很多刚接触的同学以为配了镜像就万事大吉,结果节点一挂消息全丢,就是这个基础概念没搞清。
3. 从零搭建 RabbitMQ 高可用集群
3.1 环境规划与部署方式
搭建之前先把架构规划好。我这里以三节点为例,因为三节点是最基本的高可用形态,既能容忍单节点故障,又不会像双节点那样出现脑裂时谁也说服不了谁的问题。
规划时注意几点:三个节点的 RabbitMQ 版本必须一致,尽量连 Erlang 版本也一致,否则可能出现跨版本兼容问题;生产环境建议用 RPM 或二进制包部署,便于 systemd 管理,磁盘 IO 也更稳定,Docker 更适合测试和快速验证;内存方面,单节点建议至少 8GB,因为 RabbitMQ 有内存水位限制,节点内存太小,一个高峰就频繁触发流控,体验非常糟糕。
测试环境图省事可以用 Docker Compose。给一个最小可用的三节点编排示例:
version: '3.8' services: rabbit1: image: rabbitmq:3.8.23-management hostname: rabbit1 environment: - RABBITMQ_ERLANG_COOKIE=sharecookie - RABBITMQ_DEFAULT_USER=admin - RABBITMQ_DEFAULT_PASS=admin123 ports: - "15671:15672" - "5671:5672" rabbit2: image: rabbitmq:3.8.23-management hostname: rabbit2 environment: - RABBITMQ_ERLANG_COOKIE=sharecookie ports: - "15672:15672" - "5672:5672" rabbit3: image: rabbitmq:3.8.23-management hostname: rabbit3 environment: - RABBITMQ_ERLANG_COOKIE=sharecookie ports: - "15673:15672" - "5673:5672"3.2 普通集群搭建三步走
用 Docker 编排起来之后,进到容器里执行集群命令。如果是 RPM 方式部署,步骤一模一样,区别只是命令在宿主机上执行。核心三步:
第一步,准备好一致的主机名解析。三个节点都要配置 /etc/hosts,把 rabbit1、rabbit2、rabbit3 映射到对应 IP。主机名必须和 RabbitMQ 的节点名一致,否则 join 集群的时候会找不到节点。
第二步,保证 Erlang Cookie 一致。RabbitMQ 节点之间的认证依赖 .erlang.cookie 文件,各节点必须一致。Docker 方式可以通过环境变量 RABBITMQ_ERLANG_COOKIE 设置,RPM 方式则需要手动把 master 节点的 cookie 复制到其他节点,同时注意文件权限必须是 400,属主必须是 rabbitmq 用户。
第三步,把节点加入集群。在 rabbit2 上执行:
rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl join_cluster rabbit@rabbit1 rabbitmqctl start_appstop_app停的是 RabbitMQ 应用,不是 Erlang 虚拟机;reset会清空该节点的元数据,注意确认节点本来就是新的,如果上面有历史数据,reset 会丢东西。join 成功后 start_app,节点就加入集群了。rabbit3 同样操作,只是join_cluster目标还是 rabbit@rabbit1,让 rabbit3 和 rabbit1 建立关系。
全部执行完,用下面的命令验证集群状态:
rabbitmqctl cluster_status输出的 Cluster Status 里能看到三台节点都在线,磁盘节点列表三个,running 节点三个,这就表示普通集群已经搭好。
3.3 用 Policy 配置镜像队列策略
普通集群搭好之后,队列还只是“逻辑集群、物理单点”。真正的关键步骤是配置镜像策略。最直接的配置方式,给所有队列做三副本全镜像:
rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all","ha-sync-mode":"manual","ha-promote-on-failure":"when-synced"}' --priority 0但我不会建议你直接全量镜像。全量镜像看着省事,实际会让每条消息写入三份,性能和磁盘代价都不小。更好的做法是只对核心业务队列做镜像。比如订单相关的队列统一以 order. 开头,那就只配这一组:
rabbitmqctl set_policy order-ha "^order\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"manual","ha-promote-on-failure":"when-synced"}'这条策略的意思是:所有以 order. 开头的队列,保留两个副本(一个 master,一个 mirror),不同步完成不晋升。exactly 模式是介于 all 和 nodes 之间的折中方案,副本数固定为 2,新节点加入集群时不会自动增加副本数量,删掉节点时也不会自动减少,非常稳定。
3.4 高可用效果验证
配置不能拍脑袋,必须实际验证。我最常用的一套验证流程:
- 在管理界面或者命令行创建一个测试队列,比如 order.test,发 10 条消息进去。
- 在管理界面的 Queues 列表里点开这个队列,查看节点信息。如果镜像生效,你能看到 master 对应的节点,以及 mirrors 对应的节点,两个节点名会列出来。
- 直接停掉 master 所在的节点。比如 master 在 rabbit1 上,执行
rabbitmqctl stop_app(或直接停容器模拟宕机)。 - 回到管理界面,重新看这个队列,你会发现 master 已经自动切换到了 rabbit2 或 rabbit3,消息仍然在,消费者也能正常消费。
我第一次做这个验证的时候踩过一个很小的坑:消费者是原生 Java 客户端,连接的是 rabbit1 的地址,rabbit1 一停,消费者立刻抛连接异常,要等自动恢复触发才重新连上。后来把客户端改成配置多个节点地址,单个节点故障才能做到无感知切换。这个细节正是下一章要展开的。
4. 客户端侧的高可用配合
4.1 连接工厂:自动恢复与多节点地址
服务端做了镜像,客户端跟不上,高可用就是一句空话。最常见的问题是:客户端写死了单节点连接地址,master 一挂,消费者全部断连,队列可用但没人消费,业务照样堵。
Java 官方客户端默认启用了自动恢复,但你要确认两点。第一,确认自动恢复开关是开着的;第二,配置连接地址时,尽量传入多个节点地址,这样单节点故障时连接可以直接切换到另一个节点。
ConnectionFactory factory = new ConnectionFactory(); factory.setUsername("admin"); factory.setPassword("admin123"); factory.setAutomaticRecoveryEnabled(true); factory.setTopologyRecoveryEnabled(true); Address[] addresses = new Address[] { new Address("10.0.0.1", 5672), new Address("10.0.0.2", 5672), new Address("10.0.0.3", 5672) }; Connection conn = factory.newConnection(addresses);注意setTopologyRecoveryEnabled(true)这个开关。拓扑恢复的意思是,消费者连接断开重连之后,客户端会重新声明交换机、队列、绑定关系,把因为服务端重启而丢失的拓扑重新建起来。如果不开启,服务端某个节点重启后,你可能会发现队列还在,但消费者不消费了。
Spring AMQP 的用户注意,spring.rabbitmq.addresses 同样支持逗号分隔的多地址配置:
spring.rabbitmq.addresses=10.0.0.1:5672,10.0.0.2:5672,10.0.0.3:56724.2 生产者确认与消费者确认
高可用不只是“消费者还能连上”,还要保证“数据不丢”。生产者发送消息,如果只是发完就完事,消息到底有没有到 broker,你是不知道的。需要开启生产者确认(Publisher Confirms),确认消息真正写入 master 和同步策略认可之后,才认为发送成功。
原生 Java 客户端开启确认的一段代码:
Channel channel = connection.createChannel(); channel.confirmSelect(); channel.basicPublish("exchange.order", "order.created", null, messageBody.getBytes()); if (!channel.waitForConfirms()) { // 发送失败,重试或记录 }Spring AMQP 则用 spring.rabbitmq.publisher-confirm-type=correlated 开启。开启之后,消息发出去会有一个回调,确认到达 broker。注意,publisher-confirm 只保证“broker 收到了”,不保证“mirror 同步完成了”,后者由镜像同步策略保证。两者配合起来,才能做到数据链路可靠。
消费者侧,核心是手动确认 + 合适的 prefetch。手动确认意味着消费逻辑处理成功后才调用 basicAck,没有 ack 的消息会在消费者断开后重新入队,保证不丢消息。prefetch 则控制消费者同时能从队列取走多少条未确认消息,设置太小吞吐低,设置太大一条消息处理慢了,其他消息全堆积在消费者本地,服务端 unacked 数字暴涨,一旦消费者重启,大量消息重新入队,可能引发重复消费风暴。我的建议是先从 prefetch=50 开始调,根据实际处理耗时微调,处理耗时几十毫秒的服务可以拉到 100 到 200,但尽量不要太大。
4.3 消费幂等设计
提到重复消费,很多同学第一反应是“消息队列为什么会重复”。实际上 RabbitMQ 的投递语义是 at least once,也就是至少一次。消费者处理完消息,在 ack 发送给 broker 的过程中网络断了,broker 没收到 ack,会认为消息没被消费,重新投递给其他消费者。此时业务已经处理完了,但第二条一模一样的消息又来了。
解决重复消费的标准答案,是消费端幂等。我常用的方案是:生产者生成消息时带一个全局唯一 ID,比如订单号加上一个 UUID;消费者拿到消息后,先去去重表检查这个 ID 是否处理过,处理过就直接 ack,没处理过才执行业务逻辑,再写入去重表。
String msgId = orderEvent.getMessageId(); // 用 Redis 做去重,5 分钟窗口足够覆盖大部分重复投递场景 if (redis.opsForValue().setIfAbsent("msg:" + msgId, "1", Duration.ofMinutes(5))) { // 首次消费,执行业务逻辑 handleOrderEvent(orderEvent); } // 已经处理过,直接 ack channel.basicAck(deliveryTag, false);数据库唯一索引也是通用的兜底方案,业务表加一个 message_id 唯一约束,重复插入自然失败,不影响主流程。生产环境我建议双保险:消费前用 Redis 或数据库做第一道幂等,业务表再保留一个唯一键做第二道兜底。有了幂等,即使客户端在高可用切换、重连、重投的链路里出现任何意外,也不会造成数据错误。
5. 生产环境常见问题与排查实录
5.1 网络分区导致脑裂
集群高可用最常见的拦路虎不是单节点宕机,而是网络分区。三个节点在两个机房里,中间交换机抖动几秒钟,RabbitMQ 集群会认为出现了分区。分区之后集群内部分裂成两个小组,每个小组都认为自己才是可用的一方,可能导致消息双写、队列双主,这种情况比单节点宕机更难恢复。
默认配置下,RabbitMQ 采用 pause_minority 策略:集群检测到分区后,少数派节点会直接暂停(pause),以此避免脑裂。这也是我推荐保留的默认行为。当网络恢复后,被暂停的节点需要手动重启,重启后会自动重新加入集群。
排查网络分区,重点看管理界面的 Nodes 区域,出现 partition 标志就说明已经发生过。或者执行:
rabbitmqctl cluster_statusPartitions 字段如果有内容,说明集群处于分区状态。处理方式:修复网络问题后,重启被暂停的节点,让整个集群恢复正常。这里我没有推荐把 partition_handling 改为 autoheal,因为 autoheal 在分区恢复时虽然会自动协商,但协商过程中可能以少数派数据为准,反而引发消息丢失。稳妥起见,生产环境还是 pause_minority 加人工介入。
5.2 镜像不同步:明明配了镜像,节点挂了还是丢消息
我接手过一个真实案例。团队配了 ha-mode=all,节点挂了一个,恢复之后集群状态看着正常,但后来另一个节点也挂了,消息丢了不少。查到最后,发现第一个节点挂掉恢复后, mirror 一直处于 unsynchronised 状态。原因是同步模式配的是 manual,节点恢复后没有自动同步,第二个节点一挂,这个 unsynced 的 mirror 不仅帮不上忙,按 when-synced 的晋升策略它甚至不能晋升。
排查方法:
rabbitmqctl list_queues name slave_nodes synchronised_slave_nodes如果 synchronised_slave_nodes 里缺失了某个节点,说明该节点上的镜像没有同步完成。手动触发同步:
rabbitmqctl sync_queue order_queue这里有一个经验:ha-sync-mode=manual 虽然更能控制同步时机,但也要求你建立配套的巡检机制,定期检查 unsynced 队列。生产环境我会写一个定时脚本,每天检查一次 synchronised_slave_nodes,发现异常立刻告警,并在业务低峰自动触发 sync_queue。否则 manual 模式省下来的力,会在某一天变成事故加倍还给你。
5.3 消息堆积和 unacked 暴涨
线上常见的告警是 ready 消息堆积,或者 unacked 数量异常。大多数人看到堆积本能地觉得“消费者处理太慢了”,但有一种更容易被忽略的情况:消费者代码在处理消息时抛了异常,没有捕获也没有手动 nack/ack,消息一直处于 unacked 状态。服务端不会重新投递这条消息,如果消费者线程卡住了,unacked 越积越多,看起来就是消费者没有消费新消息。
排查顺序我建议这样:
- 管理界面 Queues 页面看 ready 和 unacked 两个数字。unacked 一直涨,说明消费端拿了消息但迟迟没有确认。
- 看消费者日志,确认是否有异常堆栈或者死锁。
- 确认 prefetch 设置是否合理。prefetch 太大时,一次拉取几百条消息到本地,业务处理慢,服务端仍然把消息发给这个消费者,unacked 自然暴涨,其他消费者反而拿不到消息。
还有一个我踩过的坑:消费逻辑里没有把 ack 放进 finally 块。如果业务代码抛异常,ack 永远不执行,消息就一直卡在 unacked。正确的姿势是在 finally 里根据处理结果决定 ack 还是 nack,或者用 try-with-resources 模式确保 channel 的 ack/nack 一定会被调用。
5.4 磁盘告警、内存告警与节点被阻塞
RabbitMQ 有内存水位和磁盘空闲限制两个机制。内存使用超过 vm_memory_high_watermark(默认是物理内存的 40%)时,broker 会阻塞所有连接的生产者,防止内存继续暴涨;磁盘空闲空间低于 disk_free_limit 时,同样会阻塞写入。
这两个机制本意是保护节点不被打爆,但如果你没有提前配置,很容易触发。比如默认 disk_free_limit 在部分版本里是 50MB,这明显太小。生产环境建议手动设置:
rabbitmqctl set_disk_free_limit 2GB # 或者按内存比例 rabbitmqctl set_disk_free_limit "mem_relative" 1.5镜像队列会让这个问题更严重:一条消息原来只写一份,三副本镜像变成写三份,磁盘消耗三倍,同步过程中还会产生临时文件。我在一个日志量大的集群上就遇到过,镜像策略从 two 改成 three 之后,磁盘用量一夜之间涨了 60%,直接把 disk_free_limit 触发,生产者全部被 block。所以调整镜像策略之前,一定要评估磁盘的余量。
另外,内存水位 40% 在某些大内存机器上可能太保守。比如 64GB 内存的机器给 RabbitMQ 用,40% 就是 25.6GB,消息量不大时完全浪费。可以适当调高到 0.5,但如果机器内存本身不大,建议保持默认别乱调。调内存水位的命令:
rabbitmqctl set_vm_memory_high_watermark 0.5最后分享一个我个人的习惯,高可用架构配好之后,至少要亲手做一遍完整的故障演练:停 master、看 mirror 晋升、看客户端恢复、看消息不丢、看数据能续传。这套流程走顺了,线上真正出问题时你手不会抖。RabbitMQ 高可用做的是水磨工夫,把基础原理吃透、把每个参数理解透、把每种故障演练过,比什么骚操作都管用。