不增加副本,只重构接管路径:RocketMQ 单写有状态服务的高可用新设计

作者:金融通

云上有状态消息系统的高可用,往往受到一个困难的工程权衡限制:成本、稳态性能与故障切换速度很难同时兼顾。应用层多副本可以缩短恢复时间,却会带来额外资源和运维复杂度;单副本部署保留了成本与性能优势,但在 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 服务层引入额外业务数据复制。

这带来五个关键设计点:

  1. 无需额外业务数据复制,正常数据路径保持不变。 保留单副本、单机文件系统和云块存储的正常读写语义,不引入服务层额外写放大。

  2. 协议级安全接管,并验证所有权转移。 Multi-Attach 让同一块盘提前对两台节点可见;NVMe PR 将写权限控制下沉到存储协议层,非持有者的写请求由设备侧拒绝。

  3. 形成面向本地文件系统的接管一致性闭环。 获取写权限、清理旧缓存、挂载文件系统、执行 journal/WAL 恢复以及恢复流量,都由同一状态机约束。

  4. 去中心化故障探测,关键恢复路径不依赖中心化组件。 Master 与 Shadow 直接通过共享盘中的 lease 完成健康探测和接管判断,不要求 Controller、ZooKeeper 或 etcd 参与关键路径;即使 Kubernetes 控制面暂时不可用,节点仍可依据共享盘上的持久化状态推进切换。

  5. 以最小权限完成生产落地。 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 中恢复,并希望尽量保留本地文件系统性能与现有存储引擎。除了消息系统,同样的思路也可用于部分数据库、搜索与日志服务,但必须逐项验证设备协议、缓存语义和应用恢复能力,不能只把"抢锁"命令移植过去。

更一般地看,这项实践给出三条经验:

  1. 先区分正常路径与故障路径。 高可用不一定要求重构整个存储系统;如果稳态路径已经成熟,可以把改造集中到所有权转移和恢复编排。

  2. 把安全边界下沉到真正能够强制执行的位置。 角色选举只表达意图,设备协议的写拒绝才是防止 split-brain 的最终约束。

  3. 恢复不是一个动作,而是一条状态化链路。 故障探测、所有权确认、缓存清理、文件系统恢复、应用恢复和流量上线必须形成闭环,并能够被观测、审计和持续演练。

结语

高可用不存在一条适用于所有场景的路线。对 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

doi.org/10.1145/380...

5 阿里云官方文档:云消息队列 RocketMQ 版实例容灾

help.aliyun.com/zh/apsaramq...


线下活动推荐:

你的 AI Agent,是不是还在用昨天的数据做今天的决策?

8 月 28 日(周五)13:30-16:30,阿里云 ApsaraMQ × IBM Confluent 联合在上海举办「AI 实时数据沙龙」,带你跨越 AI 实时上下文鸿沟。

点击此处立即免费报名(仅 50 席,审核制)。

相关推荐
三翼鸟数字化技术团队3 小时前
Apache Paimon:一种新的数据入湖实践
apache·apache hive
孫治AllenSun6 小时前
【RocketMQ】事务消息详解
rocketmq
蜀道山老天师8 小时前
Zabbix监控Apache与Nginx应用实践完整指南
linux·运维·nginx·apache·zabbix
肠畔码农8 小时前
深度解析 RocketMQ 死信队列(DLQ):消费重试的终点站、存储隔离与架构容灾本质
架构·rocketmq
SelectDB技术团队10 小时前
Apache Doris 与 StarRocks 深度对比:2026 年 OLAP 引擎选型指南
人工智能·apache·知识图谱
SeaTunnel11 小时前
从 JSON 到 JSONL,Apache SeaTunnel 如何解决HTTP 大数据传输的内存难题?
http·开源·json·apache·数据集成·seatunnel
SamChan9012 小时前
对比 4 种主流 PDF 文档解析方案:PyMuPDF vs pdfplumber vs Apache PDFBox vs 大模型 OCR
python·ai·pdf·ocr·apache·机器翻译
海兰1 天前
【Kafka学习3】Apache Kafka 本地部署环境快速入门指南
学习·kafka·apache
海兰1 天前
【Kafka学习2】Apache Kafka 典型应用场景
学习·kafka·apache