作者:金融通
云上有状态消息系统的高可用,往往受到一个困难的工程权衡限制:成本、稳态性能与故障切换速度很难同时兼顾。应用层多副本可以缩短恢复时间,却会带来额外资源和运维复杂度;单副本部署保留了成本与性能优势,但在 Kubernetes 中,其恢复路径又容易被云盘 detach/attach、调度和挂载流程拖入分钟级长尾。
阿里云消息团队长期研发和运维 Apache RocketMQ,基于真实故障、容灾演练和规模化运维经验,我们重新设计了单写有状态服务的接管路径:正常运行时继续使用单副本、单机文件系统和云块存储;故障时利用 Multi-Attach 与 NVMe Persistent Reservation(下文简称 NVMe PR)完成协议级 fencing,在不增加 RocketMQ 服务层业务数据副本的前提下实现秒级接管。
这项工作已经被 FSE 2026 Industry Papers Track 录用。
从单个实例故障到规模化高可用问题
当同一套架构承载数千个租户隔离集群时,高可用就不再只是单个实例的问题,而成为一个规模化交付问题。
这些集群通常单体规模不大,但数量多、故障分散,而且对成本更敏感。除了节点宕机,我们还需要处理 I/O hang、kubelet 异常、网络分区,以及"进程仍然存活、但持久化已经停止推进"等不完全故障。此时,只看平均恢复时间并不够;关键路径、恢复长尾、状态可观测性和演练可重复性都会影响整体 SLO 与 on-call 成本。
我们将这一问题概括为一个实践中的"三角权衡":
- 低成本:不在服务层预留额外物理副本,减少冗余资源和写放大。
- 高稳态性能:保留本地文件系统、page cache、批处理等成熟 I/O 优化,这里也涉及到对社区存储功能的兼容。
- 快速且可控的恢复:端到端接管链路足够短,并尽量消除不可预测的控制面长尾。
现有路线通常能够稳定满足其中两项,却难以同时兼顾三项。
| 路线 | 主要优势 | 主要代价 |
|---|---|---|
| 应用层多副本与选主 | 故障后可快速切换 | 额外计算和存储资源,加上网络带宽开销、复制链路写放大、尾延迟与一致性运维复杂度 |
| Kubernetes 单副本 + PV 迁移 | 成本较低,保留本地文件系统性能 | 恢复依赖驱逐、调度与 detach/attach/mount 重试,I/O hang 和网络异常下容易出现分钟级长尾 |
| 分布式文件系统或者共享存储 | 减少云盘迁移步骤 | 共享 I/O 协调可能影响稳态性能,存量系统迁移和长期运维成本较高 |
云块存储本身通常已经提供存储层冗余。对大量中小集群而言,如果服务层再为故障接管保留一份完整业务副本,资源放大会非常显著。因此,我们的问题不是"如何做一个更快的复制协议",而是:能否不改变正常数据路径,只重构故障接管路径?
核心设计:正常路径不变,只重构接管路径
我们的基本原则是:Broker 正常运行时仍把 commitlog、消费队列和元数据写入单机文件系统,文件系统位于云块存储之上;不要求重写 commitlog/WAL,不把正常读写迁移到共享 WAL 或对象存储,也不在 RocketMQ 服务层引入额外业务数据复制。
这带来五个关键设计点:
-
无需额外业务数据复制,正常数据路径保持不变。 保留单副本、单机文件系统和云块存储的正常读写语义,不引入服务层额外写放大。
-
协议级安全接管,并验证所有权转移。 Multi-Attach 让同一块盘提前对两台节点可见;NVMe PR 将写权限控制下沉到存储协议层,非持有者的写请求由设备侧拒绝。
-
形成面向本地文件系统的接管一致性闭环。 获取写权限、清理旧缓存、挂载文件系统、执行 journal/WAL 恢复以及恢复流量,都由同一状态机约束。
-
去中心化故障探测,关键恢复路径不依赖中心化组件。 Master 与 Shadow 直接通过共享盘中的 lease 完成健康探测和接管判断,不要求 Controller、ZooKeeper 或 etcd 参与关键路径;即使 Kubernetes 控制面暂时不可用,节点仍可依据共享盘上的持久化状态推进切换。
-
以最小权限完成生产落地。 PR、缓存处理和挂载等高权限操作收敛到节点侧 Agent,业务容器不需要获得系统级设备权限,关键动作可以鉴权、审计和演练。
为了便于理解,先简单解释两个关键能力。
- Multi-Attach(多重挂载)指同一块云盘可以同时挂载到多台 ECS 实例,让新旧节点在任何时刻都能看到同一份数据,这是秒级接管的前提;但它只解决可见性,并不保证单写。详见通过多重挂载功能将单块云盘挂载至多台 ECS 实例 ** **1 。
- NVMe Persistent Reservation(NVMe PR,预留)是 NVMe 协议定义的一套分布式锁机制,沿袭自 SCSI 的 Persistent Reservation。主机通过 nvme pr 相关命令完成 register、acquire、preempt、release 等操作,设备侧只接受 reservation holder 的写请求,其余主机的写在存储协议层被直接拒绝;接管时新节点先抢占 reservation 再上线,这正是协议级 fencing 的实现基础。在 Kubernetes 场景下可参考使用 NVMe 云盘多重挂载及 Reservation 实现应用间的数据共享 ** **2 。
需要特别说明,Multi-Attach 只解决"两个节点都能看到同一块盘",并不自动保证单写。真正避免 split-brain 的是 NVMe PR:任意时刻只有 reservation holder 可以向设备写入。旧节点即使仍在运行、仍能看到块设备,其写请求也会在存储协议层被拒绝。
本方案的实现有两个前提:
其一是 crash-consistent 恢复能力:应用与文件系统在非优雅崩溃后,必须能自行恢复到一个一致、可用的点。RocketMQ 的 CommitLog、RocksDB 以及 ext4 的 journal replay 都具备这一能力,这是新节点接管后能直接把服务拉起来的基础。
其二是存储产品与链路约束:共享云盘必须同时支持 Multi-Attach,并以 NVMe Reservation 的形式呈现给主机,从而可以用 nvme pr 相关命令在存储协议层完成加锁,保证任意时刻只有一个写者。缺少其中任一能力,协议级 Fencing 无法成立。
整体架构与组件职责

图 1:依次展示正常运行、Node A 故障后的接管,以及 Node A 恢复后的回切。
生产部署采用两节点配对模式。两台节点承载两块共享盘和两个 Broker 实例:正常情况下,Node A 运行 Master Broker A 和 Shadow Broker B,Node B 运行 Master Broker B 和 Shadow Broker A。两块盘都以裸块设备的形式提前暴露给两台节点,但只有 PR holder 才能挂载对应数据区并写入。这样既保持了正常情况下的资源利用率,也避免故障时临时创建 Pod、重新调度以及等待卷迁移。
这里的 Shadow Broker 不等同于传统 Slave Broker。Slave Broker 属于应用层复制链路,需要持续从 Master 同步 commitlog 等业务数据并维护独立副本;故障后,再通过选主或控制面将这份已有副本提升为 Master,因此稳态会持续消耗副本计算、存储和网络带宽。Shadow Broker 则不复制业务数据:稳态不挂载 data area、不提供业务流量,只读取 probe area 中的持久化 lease,并保留轻量的接管能力。发生故障后,Shadow 不是提升一份本地副本,而是通过 NVMe PR 获取原共享盘的写权限,完成缓存清理、文件系统挂载和 crash recovery,再上线为 Master。简而言之,Slave 是"先复制数据再切换",Shadow 是"平时探测,故障时安全接管原盘"。因此,Shadow 的存在并不意味着在 RocketMQ 服务层额外维护了一份业务数据副本。
系统由四类组件组成,职责边界尽量收窄:
- Application:在本文中是 RocketMQ Broker。它只访问已经挂载好的目录,继续使用原有 commitlog、索引、缓存和崩溃恢复逻辑,不直接执行 PR、mount 或缓存清理。
- Shared Disk:唯一的持久化状态载体,逻辑上分为 probe area 和 data area。probe area 保存 owner、版本号和 processing 等 lease 信息,并通过 direct I/O 等方式避免主机缓存干扰;data area 使用 ext4、XFS 等单机文件系统保存业务数据。
- Prober:Master 侧持续推进 lease,Shadow 侧观察持久化版本是否变化,并驱动 Shadow、Promoting、Master、Demoting 四个状态之间的转换。它负责判断和编排,但不持有系统级设备权限。
- Node Agent:运行在节点侧,执行 PR 查询与抢占、目标设备缓存清理、mount/unmount 等高权限操作。接口采用鉴权、参数白名单和审计日志,避免将 SYS_ADMIN 等权限交给 Broker 容器。
probe area 与 data area 可以通过物理分区、逻辑设备或文件系统保留区域实现。关键在于 lease 必须反映真实持久化进展,业务数据区则继续使用本地文件系统语义。
从故障发现到 Broker 上线
一次完整接管可以拆成五个阶段。
1. Detect:判断持久化是否仍在推进
Master 定期把递增版本写入共享盘上的 lease。Shadow 直接从同一块盘读取该版本;连续一个探测窗口没有看到版本推进时,才触发接管。这里判断的不是"进程是否存在",而是原 Master 是否仍能完成持久化写入。因此,节点没有宕机但 I/O 已经卡住的情况,也能够被覆盖。
检测过程不依赖两台机器的本地时钟做绝对时间比较,也不要求集中式 Controller、ZooKeeper 或 etcd 参与关键路径。控制面暂时不可用时,节点仍可依据共享盘上的持久化事实完成判断。
2. Inspect:确认当前持有者和恢复状态
Node Agent 先通过 reservation report 读取当前 holder,并检查 holder key 中的 processing 标记。如果对端正在执行 journal replay、文件系统检查或 Broker 恢复等不可被打断的阶段,接管方会先退避,而不是立即再次抢占。结合预期 holder 校验,这一过程形成类似 CAS 的所有权转换,避免两个恢复流程互相打断。
3. Fence:抢占写权限并处理旧缓存
接管节点在获取 PR 之前,先清理目标块设备在本机可能残留的缓存;随后执行 acquire 或 preempt,将 reservation 原子地转移给自己。操作完成后,Agent 再次查询 holder,只有确认自己已经获得写权限,流程才会继续。
确认所有权后还需要再次清理目标设备缓存。这两次清理分别关闭两个风险窗口:第一次避免接管前残留的旧缓存进入后续恢复;第二次清除抢占过程中可能被重新填充的缓存。否则,即使设备侧已经完成 fencing,主机仍可能从过期缓存读取旧数据,甚至在稍后回写时破坏新主已经恢复的状态。
4. Recover:恢复文件系统与 Broker 状态
只有完成 PR 所有权确认和第二次缓存清理以后,新节点才允许挂载数据区。文件系统先通过 journal replay 等机制恢复到 crash-consistent 状态,随后 RocketMQ 使用已有的 commitlog/WAL 恢复流程重建可服务状态。恢复所需时间属于应用和数据规模相关成本,可以通过减少无效扫描、优化恢复并发和压缩恢复路径继续降低。
5. 上线:恢复业务流量
Broker 完成恢复并通过 readiness 检查后,才升级为 Master 并对外提供服务。任何前置步骤失败,状态机都不会越过安全边界直接上线。旧节点恢复后,如果配置了 preferred master,也通过同一套"停止服务、卸载、重新抢锁、清缓存、挂载与恢复"流程完成回切,而不是依赖控制面强制搬盘。
显式状态机与三个安全不变量

图 2:Shadow 负责探测,Promoting 完成 PR 获取、挂载与恢复,Master 执行健康检查并续租,Demoting 停止服务并卸载设备。
显式状态机的价值,是把"能看到盘""获得写权限""完成恢复"和"可以上线"区分为不同阶段。Shadow 不挂载数据区、不提供业务流量;Promoting 独占所有权转移与恢复过程;Master 持有写权限并续租;Demoting 先停止服务,再卸载设备并回到 Shadow。
我们用三个安全不变量约束整个流程:
- I1:任意时刻至多存在一个持久化写者。 这一点由 NVMe PR 在设备层强制保证,而不是依赖两端对角色状态的主观判断。
- I2:接管后不能使用旧主留下的陈旧缓存。 PR 前后的两次目标设备缓存清理,确保新主基于当前持久化状态恢复。
- I3:恢复必须独占。 processing 标记、holder 校验和退避机制保护 journal replay、fsck 与 WAL recovery,避免恢复过程中再次发生所有权争抢。
除了前面提到的 Multi-Attach、正确的 NVMe PR 语义与 crash-consistent 恢复能力,安全保证还有一个工程前提:所有高权限操作都必须由可信 Node Agent 执行。该方案解决的是节点故障、I/O stall、部分网络分区及控制面延迟下的单写接管,不提供永久共享盘损坏后的可用性,也不支持真正的多写者语义。
为什么保持 RocketMQ 正常存储路径很重要
另一类技术路线会重构存储引擎,引入共享 WAL、对象存储或远端分层存储,使计算节点尽量无状态化。这类架构有其适用场景,但对已经大规模运行的单主有状态服务,迁移成本、性能模型和长期兼容性都需要重新评估。
本文方案保留 RocketMQ 已有的单机文件系统和正常读写语义,只重新设计故障接管路径。由于高可用机制不替换正常存储引擎,RocketMQ 可以继续复用成熟的 commitlog、page cache 和崩溃恢复能力,也能最大程度保持与 Apache RocketMQ 社区版本的兼容性。冷读流控、压缩、加密以及未来新增的存储特性,原则上仍沿用原有数据路径,不需要为高可用机制重新实现一套。
这种选择的差异化价值不在于"完全没有改造",而在于把改造限制在清晰边界内:Broker负责原有业务读写和恢复,Prober 负责角色判断,Node Agent 负责设备操作,云盘协议负责最终写入互斥。对存量系统而言,这能显著降低 Broker 核心代码侵入、社区版本升级成本和长期维护负担。
实验结果与生产验证
论文基于 Apache RocketMQ 5.3.4 进行评估,并设置了四组对照:S1 是传统 Master & Slave 应用层复制;S2 是 Kubernetes 单副本 + PV 迁移;S3 使用分布式文件系统或者共享存储;S4 是本文的 Master + Shadow、Multi-Attach 与协议级 fencing 方案。它们分别代表应用复制、PV 迁移、共享存储和协议级接管四条典型路线。

论文 Table 1:节点级故障下的恢复时间
论文 Table 1:不同高可用路径在节点级故障下的恢复时间。
实验结果显示,在节点故障场景下,S2 的恢复会被 Kubernetes 调度和云盘迁移链路放大到分钟级,我们检查 Kubernetes 事件后发现,时间主要消耗在两部分:一部分是驱逐、调度和资源约束带来的不确定性;另一部分是 detach、attach 和FailedMount 重试------旧节点从控制面看仍然占有云盘时,新节点就不能立即接管。S4 则将恢复收敛到秒级。这个改进并不是简单加快某个命令,而是把传统 detach/attach 从关键路径中移除,只保留故障探测、协议级抢锁和应用恢复。
论文将端到端恢复时间概括为:
RTO ≈N + M
其中,N是由探测周期和连续未推进轮次决定的故障发现窗口;M是后续协议接管与恢复总时间,包含 PR 获取或抢占、所有权确认、缓存处理、挂载、文件系统恢复和 Broker recovery。传统 detach/attach 不再位于关键路径中。
M 不是固定常数:协议接管和挂载通常较短,Broker recovery 取决于日志规模和恢复实现,也可继续优化。

论文 Table 2:稳态吞吐与延迟
论文 Table 2:1KB 消息 send-only 负载下的稳态吞吐与延迟。
稳态性能方面,S4 的吞吐整体接近原生单副本基线。Shadow 的资源消耗也很低,因为它不复制业务流量,只进行轻量 lease 探测。换句话说,秒级接管没有以额外业务数据副本和持续写放大为代价。
在不同探测窗口以及节点宕机、网络异常、I/O hang 等故障下,实测恢复时间整体贴近 N + M:N决定故障发现窗口,M覆盖后续协议接管和 Broker 恢复。结果没有重新出现 detach/attach 带来的分钟级长尾。该架构已经在阿里云 RocketMQ 服务中投入生产,并完成数万次容灾演练。持续演练的意义不仅是证明"能够切换",更是验证每个状态转换、权限操作和回退路径都能在真实环境中重复执行。
这里的"无需复制"特指 RocketMQ 服务层不为接管预留额外业务数据副本,并不意味着底层云块存储没有可靠性冗余。协议级 fencing 解决的是故障时谁可以继续写的问题;底层数据持久性仍由云盘自身的可靠性机制负责。
可复用的工程经验
这套设计首先适用于具备以下特征的系统:单写者、状态保存在可 Multi-Attach 的块存储上、能够从 crash-consistent 文件系统和 WAL 中恢复,并希望尽量保留本地文件系统性能与现有存储引擎。除了消息系统,同样的思路也可用于部分数据库、搜索与日志服务,但必须逐项验证设备协议、缓存语义和应用恢复能力,不能只把"抢锁"命令移植过去。
更一般地看,这项实践给出三条经验:
-
先区分正常路径与故障路径。 高可用不一定要求重构整个存储系统;如果稳态路径已经成熟,可以把改造集中到所有权转移和恢复编排。
-
把安全边界下沉到真正能够强制执行的位置。 角色选举只表达意图,设备协议的写拒绝才是防止 split-brain 的最终约束。
-
恢复不是一个动作,而是一条状态化链路。 故障探测、所有权确认、缓存清理、文件系统恢复、应用恢复和流量上线必须形成闭环,并能够被观测、审计和持续演练。
结语
高可用不存在一条适用于所有场景的路线。对 Apache RocketMQ 这类拥有成熟本地存储引擎、以单写为主且需要大规模交付的系统,我们选择保留正常数据路径,将故障接管重构为一条由持久化 lease 驱动、由 NVMe PR 强制 fencing、由节点侧 Agent 执行的短路径。
最终效果是:不在 RocketMQ 服务层增加额外业务数据副本,不牺牲单机文件系统的稳态性能,也不再让端到端 RTO 被传统卷迁移流程主导。这不是消除高可用中的所有成本,而是把成本放在可解释、可验证、可维护的边界上。
相关论文 Replication-Free Failover: Protocol-Fenced Takeover for Stateful Services 已被 FSE 2026 Industry Papers Track 录用。
相关链接:
1 通过多重挂载功能将单块云盘挂载至多台 ECS 实例
help.aliyun.com/zh/ecs/user...
2 使用 NVMe 云盘多重挂载及 Reservation 实现应用间的数据共享
help.aliyun.com/zh/ack/ack-...
3 FSE 2026 官方论文页面
conf.researchr.org/details/fse...
4 论文 DOI:10.1145/3803437.3805226
5 阿里云官方文档:云消息队列 RocketMQ 版实例容灾
help.aliyun.com/zh/apsaramq...
线下活动推荐:
你的 AI Agent,是不是还在用昨天的数据做今天的决策?
8 月 28 日(周五)13:30-16:30,阿里云 ApsaraMQ × IBM Confluent 联合在上海举办「AI 实时数据沙龙」,带你跨越 AI 实时上下文鸿沟。
点击此处立即免费报名(仅 50 席,审核制)。
