Milvus CDC 跨集群复制(Replication)技术总览:主备灾备拓扑、角色约束与故障切换机制
本文以 docs/design-docs/design_docs/cdc/user_docs/01-cdc-replication-overview.md 为骨架,结合 Milvus 仓库中 CDC 模块源码与配套运维文档展开。
Milvus CDC(Change Data Capture,变更数据捕获)用于将一个 Milvus 集群的数据变更持续复制到另一个 Milvus 集群。从 Milvus v2.6 起,CDC 可以被用来构建"主备(primary-standby)"容灾拓扑:生产流量写入主集群,一个或多个备集群持续接收来自主集群的变更,同时对外提供读服务;当主集群不可用或需要维护时,可将服务流量切换到备集群,从而显著缩短业务中断时间。读完本文,你将理解 CDC 主备架构中的角色定义与行为约束、故障切换的两类手段及取舍(planned switchover 与 force failover),以及 CDC lag 对切换行为和数据安全的影响,为阅读后续的部署、切换与导入实践文档打下基础。
Milvus CDC 是什么
CDC 是一种数据同步范式:系统在捕获源端数据变化后,以流式方式将其应用到目标端。在 Milvus 中,CDC 的实现载体是一个独立的 CDC 节点(CDC node),它把"当前主集群"产生的 WAL(Write-Ahead Log)变更 持续转发给各备集群。因此,CDC 复制的对象本质上是消息流中带时序的 DML/DDL 操作序列,目标集群按序回放这些操作,从而保持与源集群一致。
值得强调的是,Milvus 的这套跨集群复制专门面向单一写入主(single-primary)的主备容灾模型设计,而不是多活(active-active)写入模型。复制的粒度是"物理通道(pchannel)"级别的复制任务——每个被复制的 DML 通道都对应一条复制链路,这正是"多通道并行复制、整体保序"的落地方式。
复制拓扑中的核心角色
一个典型的 Milvus CDC 复制拓扑包含以下组成部分(架构图):
| 组成 | 说明 |
|---|---|
| 主集群(Primary cluster) | 复制操作的源端,同时接受读和写请求;是业务写入的唯一入口。 |
| 备集群(Standby cluster) | 复制操作的目标端,持续接收主集群的变更;在保持备机身份的期间是只读的。 |
| CDC 节点(CDC node) | 转发 WAL 变更的 Milvus 组件,把当前主集群的变更投递到各备集群。凡是"可能在某次切换/故障转移后成为主集群"的集群,都应部署 CDC 节点。 |
| 复制拓扑(Replication topology) | 已配置的"源到目标"关系,例如 cluster-a -> cluster-b,由跨集群拓扑定义描述。 |
从源码结构上看,CDC 节点是一个独立的服务模块,位于 internal/cdc/server.go:CDCServer 内部持有并管理一个 controller(internal/cdc/controller/controller.go),启动时拉起控制器,停止时释放。而复制拓扑的元数据(ReplicatePChannelMeta,即每个被复制 pchannel 的复制任务元信息)持久化在 etcd 中(internal/cdc/meta/replicate_meta.go),控制器正是通过监听 etcd 中该前缀下的元数据变化来驱动复制任务的创建与回收的。
一主一备拓扑(最常见)
Application writes
|
v
Primary cluster A -- CDC replication --> Standby cluster B
主集群 A 接受应用写入,通过 CDC 将变更持续同步给备集群 B。这是最常见、最容易理解与运维的部署形态,也是后续故障切换文档讨论的基础场景。
一主多备拓扑
Primary cluster A -- CDC replication --> Standby cluster B
\-- CDC replication --> Standby cluster C
单主多备拓扑中,主集群 A 同时向多个备集群(B、C)复制数据。备集群之间没有复制关系、互不影响;当 A 故障时,你可以从多个备集群中挑选一个进行提升,提升后需相应调整复制方向。
主备集群的行为约束
| 角色 | 读(Reads) | 写(Writes) | 复制行为 |
|---|---|---|---|
| 主集群(Primary) | ✅ | ✅ | 向各备集群发送变更 |
| 备集群(Standby) | ✅ | ❌ | 接收来自主集群的复制变更 |
从上表可以看到两个关键设计:
- 备集群可以对外服务读流量。查询不会破坏一致性,因此容灾状态下读流量可以就近或持续打到备集群(前提是数据可见性预期需与主备间延迟对齐)。
- 备集群会拒绝直接写入请求。这是防止脑裂(split brain)、维持拓扑一致性的关键机制——如果备集群允许并发写入,两端数据会在各自的通道上产生无法调和的分叉。
主备角色的判定在 CDC 控制器中有明确体现:控制器从 etcd 读回复制通道元数据后,会将该通道的源通道名与当前集群 ID(paramtable 中的 CommonCfg.ClusterPrefix)比对,只有当前集群是某个复制任务的源时,才会为该通道创建 replicator(见 internal/cdc/controller/controller.go 中 recoverReplicatePChannelMeta 的实现)。这也解释了"每个可能成为主的集群都要部署 CDC 节点"的原因——角色切换后,原备集群需要立刻具备创建复制任务的能力。
由于复制方向会在主备切换时来回变化,etcd 中复制任务元数据的删除/重建需要处理异步时序冲突。源码中采用"携带 ModRevision 的乐观删除":RemoveReplicatePChannelWithRevision 通过 etcd 事务 Compare(ModRevision(key), "=", revision) 保证只有版本匹配时才删除,避免连续切换过程中出现 [Save, Delete, Save] 被乱序执行成 [Save, Save, Delete] 的竞态(详见 internal/cdc/meta/replicate_meta.go 中的注释)。
故障切换的两种选项
Milvus 提供两条把服务流量从主集群迁移到备集群的路径,核心差异在于是否允许丢数据与是否要求旧主可达:
| 操作 | 适用场景 | 数据丢失 | 预期恢复行为 |
|---|---|---|---|
| 计划切换(Planned switchover) | 旧主仍可达;或需要做维护 | RPO = 0 | 等待剩余复制数据全部就位后,再完成角色互换 |
| 强制故障转移(Force failover) | 旧主完全不可用且短期内无法恢复 | 可能丢失 | 立即提升备集群为主,尽快恢复写入 |
选择原则:
- 只要旧主还能响应,优先使用计划切换。计划切换等待剩余数据复制完成后再变更角色,保证 RPO = 0。
- 只有当"恢复可用性"比"等待旧主恢复"更重要时,才使用强制故障转移。此时数据安全让位于可用性。
计划切换(Planned switchover)
计划切换的典型场景是把拓扑 cluster-a (primary) -> cluster-b (standby) 变为 cluster-b (primary) -> cluster-a (standby),全程不丢数据。核心操作是构造一份反向的完整替换配置:cross_cluster_topology 中把源/目标对调,并把同一份配置依次应用到两端(先当前主、后当前备)。角色变更期间旧主降级为备并拒绝新写入,旧备等待剩余复制数据追平后自提升为主并恢复写入(完整流程与代码示例见 03-cdc-planned-switchover.md)。
值得注意:计划切换只是保证不丢数据,但操作耗时取决于尚待复制的数据量(即 CDC lag)。lag 越高,切换等待越久。
强制故障转移(Force failover)
强制故障转移把备集群提升为独立主集群(standalone primary):配置中只保留备集群本身、cross_cluster_topology 为空,并设置 force_promote=True,随后只向该备集群发送一次更新请求即可完成提升。该操作不等待旧主,未复制的数据会丢失。由于它通常是可用性优先的兜底手段,运维上需要特别注意:
- 提升后立即把写流量全部切到新主;
- 将旧主从写入端点、负载均衡、DNS 与自动化链路中彻底移除;
- 旧主恢复后不得再回连旧拓扑、不得接收应用写入——它可能含有从未复制到备机的"过期"数据,而新主在故障转移后可能已有新写入,二者已分叉(详见 04-cdc-force-failover.md)。
CDC Lag:复制延迟与切换风险窗口
CDC lag 定义为"已写入主集群、但尚未被应用到备集群"的数据量。它是判断复制健康度、指导切换决策的核心指标,直接影响故障转移行为:
- 计划切换时:lag 越低,通常切换完成越快(新主只需追平少量剩余数据即可接管)。
- 强制故障转移时:lag 就是旧主不可用时刻可能丢失的数据窗口——lag 越大,潜在丢失越多。
因此,官方建议持续监控 lag 并尽量压到最低。CDC lag 可能升高的情况包括:主集群写入速率高、集群间网络延迟或丢包增大、备集群过载、CDC 节点资源不足、运行大型 DDL 或导入任务等。
估算 CDC lag 的 PromQL
02-cdc-replication-quick-start.md 中给出了一个估算 lag 的 PromQL 查询(单位为秒):
clamp_min(
max by (channel_name) (
milvus_wal_last_confirmed_time_tick
- timestamp(milvus_wal_last_confirmed_time_tick)
)
-
min by (channel_name) (
milvus_cdc_last_replicated_time_tick
- timestamp(milvus_cdc_last_replicated_time_tick)
),
0
)
语义拆解:
- 对每个源通道(channel),
milvus_wal_last_confirmed_time_tick表示该通道上 WAL 最新确认的 timetick,milvus_cdc_last_replicated_time_tick表示 CDC 最近一次完成复制投递的 timetick; max by取该通道最新写入点,min by取该通道最慢的复制进度,二者之差即为滞后窗口,clamp_min(..., 0)将负值归零;- 每个 gauge 上做
- timestamp(...)是为了消除两条指标由不同抓取目标采样而产生的相位差——若直接对两个裸 gauge 做差,即使系统空闲,结果也会以约一个抓取间隔的幅度振荡。
如果 Prometheus 同时抓取多个 Milvus 集群,请附加与部署匹配的标签过滤(如 namespace、app_kubernetes_io_instance),避免把不同集群的指标混在一起。
从源码侧可以验证该指标的维护链路:复制流在推进时会更新 CDCLastReplicatedTimeTick 指标,其 LabelValues 携带源通道名与目标通道名(见 internal/cdc/replication/replicatestream/metrics.go 中 UpdateLastReplicatedTimeTick/InitLastReplicatedTimeTick 的实现);当复制任务结束或方向变更时,通过 DeleteLastReplicatedTimeTick 清理对应的时间序列,避免指标泄漏。
复制期间的数据导入
在主备复制拓扑持续运行期间,仍然可以向集群导入数据,但必须使用两阶段提交(two-phase commit,2PC)导入模式:导入在源端先进入 Uncommitted(数据不可见)状态,提交动作再作为一个"有序栅栏(ordered fence)"复制到备端,使主备两端在同一个逻辑点位让数据可见。普通的一阶段 auto_commit=true 导入在复制集群中会被拒绝。配置项 dataCoord.import.enableInReplicatingCluster 需要同时在主备两端开启,该参数支持热刷新、无需完整重启。完整步骤与错误对照表见 05-cdc-2pc-import.md。
从总览到实操:后续阅读路径
本文所属的 CDC 用户文档是一套循序渐进的系列,承接总览内容后可依次阅读:
| 文档 | 内容 |
|---|---|
| 02-cdc-replication-quick-start.md | 用 Milvus Operator 部署 source/target 两个 standalone 集群,通过 update_replicate_configuration 下发复制配置并验证同步;含 CDC lag 的 PromQL 估算示例 |
| 03-cdc-planned-switchover.md | 无数据丢失的角色互换流程(RPO = 0),含反向拓扑构造、应用顺序与切换回退 |
| 04-cdc-force-failover.md | force_promote=True 的兜底提升流程、数据丢失风险与防脑裂要点 |
| 05-cdc-2pc-import.md | 复制模式下的两阶段提交批量导入实操 |
仓库内另有若干与 CDC 相关的设计/专题文档(如 get_replicate_configuration、force_promote_failover、data_salvage_for_force_failover、per-cluster mTLS 用户指南等,位于 docs/design-docs/design_docs/cdc/),需要了解配置读取、强制提升与数据抢救的底层设计时可进一步查阅。
常见问题(FAQ)
备集群可以对外提供查询吗?
可以。备集群在保持备机身份期间能够服务读流量,但不能接受写入,只有被提升为主之后才能写入。
CDC 支持双活(active-active)写入吗?
不支持。CDC 复制专为**单主(single-primary)**拓扑设计。如果同时对多个集群写入,会造成脑裂与数据分叉,破坏一致性。
计划切换会丢数据吗?
不会。计划切换会等待剩余数据完成复制后,备集群才升为主,因此 RPO = 0。
强制故障转移会丢数据吗?
可能丢,但不是必然。若旧主故障前所有写入都已完成复制则无丢失;若存在 lag,则未复制的部分可能丢失。
强制故障转移最多可能丢多少数据?
潜在丢失量以旧主不可用时刻的 CDC lag 为上界——lag 越大,风险窗口越大。
复制拓扑运行期间还能导入数据吗?
可以。复制集群中的导入必须以两阶段提交(2PC)模式运行(auto_commit=false),具体见 Bulk Import in CDC Replication Mode(05-cdc-2pc-import.md)。普通自动提交的一阶段导入在复制集群中会被拒绝。
小结
- Milvus CDC 从 v2.6 起为跨集群主备容灾提供官方能力,核心组件是独立部署的 CDC 节点与 etcd 中持久化的每通道复制元数据。
- 复制拓扑严格单主:主集群读写、备集群只读并拒绝直接写入,从而从机制上杜绝脑裂。
- 服务迁移有两条路径:计划切换(RPO = 0,要求旧主可达)与强制故障转移(可用性优先,丢失量以 CDC lag 为界),应按业务对可用性与数据安全的需求取舍。
- CDC lag 是贯穿部署、监控与切换决策的关键指标,可使用 WAL 与 CDC 两套 timetick 指标的差值进行 PromQL 估算并持续告警。
- 结合仓库源码(internal/cdc/server.go、internal/cdc/controller/controller.go、internal/cdc/meta/replicate_meta.go)可进一步理解 CDC 节点如何通过 etcd 元数据驱动复制任务、如何在角色切换时安全回收任务,这些机制共同保证了主备方向的可靠倒换。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00