从 3.x 到 4.x——升级、迁移与 Feature Flags
RabbitMQ 系列 · 第 21/22 篇
上一篇:《集群 Peer Discovery——自动发现与 K8s 集成》
下一篇预告:《RabbitMQ 生产最佳实践——可靠性、容量与监控清单》
开头:大版本升级,踩不踩雷看这一篇
打补丁升级(3.12.13 → 3.12.14)谁都会——换包、重启、完事。但 跨大版本(3.13.x → 4.x)完全是另一回事:底座换了、特性被砍了、协议地位变了,旧集群能不能平稳渡过去,全看你升级前的功课做没做。
本篇聚焦 3.x 到 4.x 这次大版本跨越,把三件事讲透:
- 为什么 4.0 是大版本——被移除的、变核心的、换底座的;
- Feature Flags(特性开关)——让新旧版本节点短暂共存的机制,滚动升级的命脉;
- 升级实操——滚动升级步骤、维护模式、蓝绿兜底,以及回滚为什么指望不上。
本篇是第 11 篇 《集群与高可用》 和第 20 篇 《Peer Discovery》 的后续:前两篇讲"怎么把集群搭起来",本篇讲"集群搭起来之后,怎么把它平移到下一代"。
一、为什么 4.0 是"大版本"
判断一次升级是不是"大版本",最简单的标准:有没有东西被永久移除。4.0 这一波,砍得相当狠。
| 变化类别 | 具体内容 | 对你的影响 |
|---|---|---|
| 经典镜像队列被移除 | ha-mode 策略不再生效,不能再新建经典镜像队列 | 还在用经典队列 + ha-mode 做 HA 的,升级前必须迁到 Quorum 队列 |
| AMQP 1.0 升为核心协议 | 原 rabbitmq_amqp1_0 插件功能并入核心 | 想用 AMQP 1.0 不用再单独装插件;AMQP 0.9.1 仍是默认 |
| Classic v1 队列移除 | 4.0 启动时自动把残留的 v1 队列迁成 v2 | 升级那天会触发一次性迁移,数据量大就慢——提前评估(详见 第 12 篇 Classic 积压) |
| Mnesia → Khepri 元数据 | 新的 Raft 化元数据存储,故障恢复更可预期 | 默认仍是 Mnesia;Khepri 需显式启用,且 3.13 开过 Khepri 的集群无法就地升级到 4.0 |
一句话总结:4.0 把"还留着的旧方案"清了一遍。经典镜像队列让位给 Quorum 队列,Classic v1 让位给 v2,Mnesia 有了接班人 Khepri,AMQP 1.0 扶正。
这就是为什么 3.x → 4.x 不能像打补丁那样无脑升级——你得先确认"被移除的特性你到底在不在用"。
二、Feature Flags:让新旧节点短暂共存的开关
滚动升级(一次只升一个节点)的核心矛盾是:升级中途,集群里既有老版本节点,也有新版本节点。它们行为不完全一样,凭什么能正常通信、不把集群搞崩?
答案就是 Feature Flags(特性开关)。
2.1 它解决什么问题
Feature Flags 是一套"集群级开关":某个特性是否启用,由 整个集群统一 决定,不能这个节点开、那个节点关。只要开关状态一致,新旧版本节点就能判定彼此兼容、继续通信——这正是滚动升级能成立的前提。
这个机制存在的唯一目的,就是 允许不停机的滚动升级。它不是给你当业务开关用的。
2.2 三条铁律
| 规则 | 含义 |
|---|---|
| 只有所有节点都支持的 flag 才能启用 | 有一个节点不认识它,就启不了 |
| 节点 join/rejoin 的双向门槛 | 它必须支持集群已启用的所有 flag,集群里所有节点也必须支持它已启用的所有 flag——否则 join 不进来 |
| 一旦启用,无法关闭 | 所以启用要谨慎,尤其大版本相关 flag 启用后 等于断了回滚的路 |
举个例子:3.13.x 节点和 3.12.x 节点能共存,前提是 没有任何 3.13 专属的 flag 被启用。一旦你在升级中途启用了新 flag,旧版本节点就再也 join 不回来了。
2.3 三种状态与三种稳定性
先区分两组概念。
flag 的状态(state):
| 状态 | 含义 |
|---|---|
enabled | 已启用 |
disabled | 已禁用 |
unsupported | 集群里至少一个节点不认识它(启不了) |
state_changing | 启用过程中(迁移函数在跑,相关操作可能被阻塞) |
flag 的稳定性(stability):
| 稳定性 | 含义 |
|---|---|
required | 必需——升级到某个版本前必须已启用,否则新版节点拒绝启动 |
stable | 稳定,可在生产启用 |
experimental | 实验,默认不启用 |
2.4 关键命令
# 列出所有 feature flag 及其状态
rabbitmqctl list_feature_flags
# 表格形式、列全字段,可读性最好
rabbitmqctl -q --formatter pretty_table list_feature_flags \
name state provided_by desc doc_url stability
# 启用某一个
rabbitmqctl enable_feature_flag quorum_queue
# 启用所有 stable(不含 experimental)
rabbitmqctl enable_feature_flag all管理界面也能操作:Admin → Feature Flags。
enable_feature_flag all只开 stable、不开 experimental,是滚动升级结束后的标准收尾动作。
2.5 启用 flag 时内部发生了什么
启用一个 flag 不是"拨一下开关"那么简单,内部流程是:
- 校验是否已启用、是否被支持;
- 状态置为
state_changing,依赖它的组件可能 被阻塞; - 递归启用它依赖的其它 flag;
- 跑 迁移函数(比如改数据库表结构、转换数据格式);
- 全部成功才置为
enabled,否则回滚为disabled。
重点:迁移函数可能耗时——尤其涉及大量数据转换的 flag(比如 Classic v1→v2 那种)。启用时要有"集群短暂变慢/部分操作卡住"的心理准备,别在高峰期点。
2.6 flag 的"毕业"机制
flag 不是永远在那挂着。随版本演进,stable flag 会在后续版本变成 required——兼容代码被删,老节点再也 join 不进来。这就是为什么官方反复强调:
每次滚动升级完成后,立刻
enable_feature_flag all把所有 stable flag 打开。 现在偷懒不点,将来升级时就会被卡。
三、4.x 必须知道的几个 Feature Flag
挑几个对升级路径影响最大的说一下。
| flag | Stable 版本 | Required 版本 | 作用 |
|---|---|---|---|
rabbitmq_4.0.0 | 4.0 | — | 总开关:启用 4.0 的多项新特性与新行为。升级到 4.0 后不启用,节点运行在"向后兼容模式"(如新的 Quorum 队列特性、AMQP-1.0 流控都不可用) |
khepri_db | 4.0 | — | 启用 Khepri 元数据存储(Raft 化、故障恢复更可预期)。默认不启用,因影响面大需显式 opt-in |
quorum_queue | 3.8.0 | 3.11.0 | Quorum 队列支持(早就 required,老集群早就该开了) |
stream_queue | 3.9.0 | 3.12.0 | Stream 队列支持 |
direct_exchange_routing_v2 | 3.11.0 | 3.12.0 | 直连交换机路由 v2 实现 |
stream_filtering | 3.13.0 | 4.0.0 | Stream 过滤;升 4.0 前必须已启用 |
两个特别提醒:
rabbitmq_4.0.0是 4.0 的总开关。升级完所有节点到 4.0 后,不启用它,等于白升——4.0 的能力都没真正打开。khepri_db有个坑:3.13 里开过 Khepri 的集群,因为 4.0 对 Khepri 做了不兼容的大改,无法就地升级到 4.0。这种情况只能走蓝绿部署(后面讲)。如果你 3.13 没碰过 Khepri,则无所谓。
四、升级前检查清单
大版本升级,功课全在升级 之前。按这个清单逐项过:
| 检查项 | 怎么做 | 不通过的后果 |
|---|---|---|
| 升级路径是否可达 | 对照"版本可达性表"确认当前版本能否直达目标版本 | 直接升导致节点拒绝启动 |
| 所有 stable flag 已启用 | rabbitmqctl enable_feature_flag all,再 list_feature_flags 确认无 disabled 的 stable 项 | 新版节点启动失败 |
| 被移除特性是否在用 | 查策略:rabbitmqctl list_policies;查队列类型 | 经典镜像队列升级后失效、HA 丢失 |
| 经典镜像队列迁移 | 在 3.x 先把 ha-mode 队列迁到 Quorum 队列 | 升 4.0 后这些队列不再有镜像 |
| Classic v1 队列评估 | 数一下 v1 队列的数据量,估算迁移耗时 | 升级启动时一次性 v1→v2 迁移很慢,拖长停服窗口 |
| Erlang 版本 | 确认目标 RabbitMQ 版本要求的 Erlang 范围 | 节点起不来 |
| 客户端库版本 | 确认客户端 SDK 与新协议行为兼容 | 连接异常、消费失败 |
| 插件兼容性 | 核心插件随发行版一起升级;社区插件单独验证 | 插件加载失败 |
| 备份 | 备份数据目录 / 导出 definitions | 出问题无法回滚(大版本回滚基本不可行) |
| 集群健康 | 无告警、无正在进行的副本同步、负载合理 | 升级中出问题难定位 |
最容易被忽略的一项是备份。 大版本升级不像打补丁——一旦新版本节点启动并启用了新 flag,元数据格式可能被改写,再想退回旧版几乎不可能。升级前完整备份节点数据目录,是最后一道保险。
五、版本可达性与 Erlang 要求
5.1 RabbitMQ 版本可达性
官方明确的升级路径(节选):
| 从 | 到 |
|---|---|
| 4.2.x | 4.3.x |
| 4.1.x | 4.2.x |
| 4.0.x | 4.2.x |
| 4.0.x | 4.1.x |
| 3.13.x | 4.2.x |
| 3.13.x | 4.1.x |
| 3.13.x | 4.0.x |
| 3.12.x | 3.13.x |
| 3.11.18 | 3.12.x |
| 3.10.x | 3.11.x |
两条关键规则:
- 只能逐个 minor 升级,不能跳级。想从 3.11 升到 3.13,得 3.11 → 3.12 → 3.13 一步步来。
- 升到 4.3.x 只能从 4.2.x 来。3.13.x 用户想上 4.3,必须 先升到 4.2.x,再升 4.3.x,不能一步到位。
跨多个 minor 的,每一跳升完都要
enable_feature_flag all,再进入下一跳。
5.2 Erlang 版本
RabbitMQ 每个版本对 Erlang 有明确的"最低要求 / 最高支持"范围。以 4.3.x 为例,需要 Erlang 26 或 27。官方建议 用目标 RabbitMQ 版本支持的最高 Erlang,并 Erlang 和 RabbitMQ 一起升——避免"RabbitMQ 升了、Erlang 没动"踩到不兼容。
具体版本对应以官方 Erlang Version Requirements 页为准,别凭记忆。
六、三种升级策略
官方给出三种升级策略,适用场景和安全等级各不同。
| 策略 | 停服窗口 | 安全等级 | 适用 |
|---|---|---|---|
| 滚动升级(rolling / in-place) | 几乎无(单节点轮换) | 推荐 | 大多数生产集群的默认选择 |
| 蓝绿部署(blue-green) | 取决于实现(简化版有小窗口) | 最安全 | 对安全性要求极高、或滚动不可行的场景 |
| 增缩替换(grow-then-shrink) | 几乎无 | 不推荐(整集群) | 只适合替换单个节点 |
6.1 滚动升级(推荐)
一次只升一个节点:停掉节点 A → 升级 A 上的 RabbitMQ(和 Erlang)→ 启动 A → 观察 → 再处理 B。全升完后启用新版本引入的 stable flag。
前提:所有 stable flag 必须在升级 之前 就已启用;新旧版本之间必须有滚动升级路径(见版本可达性表)。
6.2 蓝绿部署(最安全)
起一套全新集群,把流量逐步切过去。安全在哪?升级过程可以中止——发现问题把应用切回老集群就行,老集群原封不动。
典型步骤:部署新版本集群 → 同步元数据 → 用 Federation/Shovel 把消息搬过去 → 切消费者 → 抽干消息 → 切生产者 → 下线老集群。
对于 3.13 开了 Khepri 无法就地升级 的场景,蓝绿是唯一出路——它本质上是"迁到新集群"而非"升级"。
6.3 增缩替换(慎用于整集群)
新加节点 D、迁副本到 D、踢掉老节点 A,循环直到集群全是新节点。官方明确不推荐用于整集群升级:它会改变副本身份、可能引发大量不必要的数据迁移。仅在替换单个需要退役的节点时才考虑。
七、滚动升级实操步骤
以 3 节点集群(A、B、C)为例,逐步走一遍。
7.1 升级前(全集群)
# 1. 确认所有 stable flag 已启用(关键!)
rabbitmqctl enable_feature_flag all
rabbitmqctl list_feature_flags # 确认无 disabled 的 stable 项
# 2. 确认集群健康:无告警、无副本同步中
rabbitmqctl cluster_status
rabbitmqctl list_queues name synchronised_mirror_pids7.2 逐节点升级(对每个节点重复)
# 1. 先确认关掉这个节点不会让任何 quorum 队列 / stream 丢仲裁
rabbitmq-diagnostics check_if_node_is_quorum_critical
# 返回非零就别停——先去恢复那个掉线的节点
# 2. 停节点
rabbitmqctl shutdown
# 3. 升级本节点的 RabbitMQ 包(和 Erlang,如需要)
# yum/apt/dpkg 视发行版而定
# 4. 启动节点,观察日志与监控
systemctl start rabbitmq-server自动化滚动升级时,用
rabbitmq-upgrade await_online_quorum_plus_one让节点停机前 阻塞等待,直到在线节点数足够维持仲裁。K8s 上的 Cluster Operator 已经把它做进了preStop钩子。
7.3 升级后(全集群)
# 所有节点都升级完,启用新版本引入的 stable flag
rabbitmqctl enable_feature_flag all
# 重新均衡队列/stream 主副本(滚动升级后分布会不均)
rabbitmq-queues rebalance all升级后清一下浏览器缓存:管理界面是 Web 应用,升级后不清缓存可能遇到 JS 报错。
八、维护模式:升级的"减震器"
滚动升级时,停掉一个节点前,最好先把它"温和地"从服务中撤出来——这就是 维护模式(Maintenance Mode)。
8.1 它做了什么
进入维护模式的节点会:
- 挂起所有客户端监听器(不再接受新连接);
- 关闭现有连接(应用应自动重连到其它节点);
- 转走本节点的 Quorum 队列主副本,并不再参与后续 Raft 选举;
- 标记自己为"维护中"。
此时再 shutdown,对集群的冲击最小——大多数职责已经提前转移了。
8.2 开启与恢复
# 进入维护模式(撤出流量)
rabbitmq-upgrade drain
# 恢复正常(如果你决定不重启了,用它回滚 drain 的影响)
rabbitmq-upgrade revive重启会自动退出维护模式,所以正常升级流程是:
drain→shutdown→ 升级 →start,不需要手动revive。revive只在"drain 完发现没法按计划重启"时才用。
用 rabbitmqctl cluster_status 或 rabbitmqctl status 查节点是否处于维护中。
九、回滚为什么指望不上
这是大版本升级最需要心理建设的一点:官方不支持、也不测试回滚(downgrade)。
- 补丁版本之间(4.3.2 → 4.3.1)回滚有时能成,但 不保证——有过连相邻补丁都回不去的先例。
- 大版本之间(4.x → 3.13.x)回滚 基本不可行:元数据格式已被改写、新 flag 已启用且无法关闭,老版本节点根本 join 不回来。
所以保险要买在升级之前:
- 完整备份节点数据目录;
- 灰度:先在非生产环境完整走一遍升级流程;
- 要绝对安全就走蓝绿——发现问题切回老集群,老集群没动过,天然可回退。
十、常见坑与排错
| 现象 | 多半原因 | 排查 |
|---|---|---|
| 升级后节点拒绝启动 | 升级前有 stable flag 没启用 | 看启动日志里的 flag 报错;回滚数据目录从备份恢复,补开 flag 重升 |
| 升到 4.3 直接从 3.13 | 没走 4.2 中转 | 3.13 只能先到 4.2,再到 4.3 |
| 升级后经典镜像队列失效 | 4.0 移除了经典镜像 | 升级前先迁 Quorum 队列 |
| 启动卡很久 | Classic v1→v2 一次性迁移在跑 | 评估 v1 队列数据量;耐心等或预先迁移(见 第 12 篇) |
| 节点 join 不回来、报 flag 不兼容 | 升级中途启用了新 flag,旧节点回不去 | 这就是"启用即不可逆"——只能继续升完,别试图回滚 |
check_if_node_is_quorum_critical 失败 | 有节点已掉线,再停一个就丢仲裁 | 先恢复那个掉线节点,再继续滚动 |
| 3.13 开过 Khepri,升 4.0 失败 | Khepri 在 4.0 大改,不兼容 | 走蓝绿部署,迁到全新 4.x 集群 |
| 管理界面 JS 报错 | 升级后浏览器缓存没清 | 清缓存、localStorage、cookie |
| Erlang 版本不对 | 用了目标 RabbitMQ 不支持的 Erlang | 对照 Erlang Version Requirements,用支持范围内的版本 |
两把排错钥匙:
- 日志:升级时盯着目标节点日志——flag 启用、迁移函数、Khepri/Mnesia 状态全在里面。
check_if_node_is_quorum_critical:停节点前的强制体检,不过这关就别停。
小结
- 4.0 是大版本:经典镜像队列移除、AMQP 1.0 升核心、Classic v1 队列启动时迁 v2、Mnesia 有了 Khepri 接班——升级前先确认"被移除的特性你在不在用"。
- Feature Flags 是滚动升级的命脉:让新旧版本节点短暂共存;三条铁律(全支持才能开、双向 join 门槛、开了关不掉)务必记牢。
- 每次滚动升级完,立刻
enable_feature_flag all——stable flag 早晚会变 required,现在不点将来卡你。 - 升级路径:只能逐个 minor 升,3.13 想上 4.3 必须先到 4.2;Erlang 版本要落在目标 RabbitMQ 的支持范围内。
- 三种策略:滚动(推荐)、蓝绿(最安全、唯一能回退、Khepri 不兼容的唯一出路)、增缩(只替换单节点)。
- 回滚指望不上:大版本升级不可逆,备份 + 灰度是底线,要绝对安全走蓝绿。
- 维护模式(
rabbitmq-upgrade drain)是停节点前的减震器,转走流量再停,冲击最小。
下一篇是整个系列的收官——《RabbitMQ 生产最佳实践——可靠性、容量与监控清单》,把前面 21 篇的要点汇成一份能直接照着做的生产清单。