MongoDB 数据同步怎么做?oplog、Change Streams、同步工具 4 种方案一次讲清(附踩坑清单)

前几天,一个做电商的朋友在群里抛了个问题:「订单数据在 MongoDB 里,现在要实时同步一份到数仓做报表,顺便再往国产库里备一份,这玩意儿到底该怎么搞?」

群里安静了几秒,然后有人甩了个链接------MongoDB 官方文档。朋友回了一句:「看过了,满屏参数,越看越懵。」

我后来琢磨,这问题挺典型的。你搜「MongoDB 数据同步」,出来的大概是三拨东西:官方文档(权威,但像天书)、某个工具的操作手册(只讲它自己,不告诉你该不该用它)、还有几篇散落的技术博客(有用,但零碎)。唯独没人把「你到底该选哪种方案」这件事讲清楚。

所以今天这篇,我把 MongoDB 数据同步的 4 条路线一次性捋清楚,配上选型对比表,再加几个我这些年踩过的坑。看完你应该能对着自己的场景,直接圈出该走哪条。

一、先把「同步」这两个字掰扯清楚

很多人一上来就懵,是因为「MongoDB 数据同步」这个词,指的压根不是一回事。往细了说,至少有两层:

  1. 副本集内部的复制(replication):主节点和从节点之间保持数据一致。这是 MongoDB 自己干的活,目的是高可用和读写分离。
  2. 跨实例、跨库的同步(sync / migration):把 Mongo 的数据搬到另一个 Mongo、关系型库、数仓,或者反向。这得靠外部工具或者自己写代码。

不把这两层分清,你就没法解释为什么官方文档和工具教程讲的完全是两码事。下面 4 条路线,第一条是「内」,后三条是「外」。

二、路线一:副本集 oplog 复制(MongoDB 自带的)

如果只是想让同一个集群里的几个节点数据一致,那 MongoDB 自己就搞定了,核心就是 oplog(operation log,操作日志)。

原理一句话:主节点每执行一次写操作,就往本地的 oplog 集合里记一条;从节点不停地从这个集合里拉最新记录,再照着重放一遍。因为记录的是「操作」而不是「结果」,所以天然是增量、且顺序严格。

从节点刚加入副本集的时候,会先做一次初始同步(initial sync) ,把全量数据拉过来;之后才进入持续复制,追 oplog 增量。怎么看追得怎么样了?两条命令:

javascript 复制代码
// 看整体副本集状态
rs.status()

// 看每个从节点的复制落后情况
rs.printSecondaryReplicationInfo()

rs.status() 里重点看每个成员的 stateStrPRIMARYSECONDARYSTARTUP2(正在初始同步)。要是从节点一直卡在 STARTUP2 不动,多半是数据量大或者网络不行。

这条路我最想提醒的坑,是 oplog 窗口(oplog window)。 oplog 是个有上限的集合,满了就把最旧的挤掉。如果从节点追得太慢,等它再想追的时候,它还没同步到的那些操作已经被主节点清掉了------这个从节点就「stale」(陈旧)了。到了这一步,增量追不回来了,只能删数据重做一次初始同步。所以生产上,oplog 的大小、从节点的复制延迟,都得盯着。

顺带说一句 oplog 大小怎么估:它至少要能覆盖「你最长的复制中断窗口 + 一次完整初始同步的时间 + 预留维护窗口」。三个数一加,再乘个安全系数,基本就不会在关键时刻掉链子。

小结:路线一解决的是「同一个副本集内部」的一致性问题,纯内部机制,不涉及「搬到别的库」。

三、路线二:Change Streams(自己写消费者)

如果你要把 Mongo 的变化实时推出去------比如推到另一个 Mongo、推到消息队列、或者触发业务逻辑------MongoDB 官方给了一套 Change Streams(变更流) 的 API。

它其实还是建立在 oplog 之上,只不过把「读 oplog」这件事包装成了订阅接口,你写个消费者挂着就能收变化。拿 Node.js 举个例子:

javascript 复制代码
const { MongoClient } = require('mongodb');

const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('orders');

const changeStream = db.collection('order_info').watch([
  { $match: { operationType: { $in: ['insert', 'update', 'delete'] } } }
]);

changeStream.on('change', (next) => {
  // next 里就是这次变更的详情,含 _id、operationType、fullDocument 等
  console.log(next.operationType, next.fullDocument);
});

思路很干净,能力也强。但我要说句实在话:Change Streams 是「半成品」,不是「成品方案」。 它只负责把变化「吐」给你,后面的事全得你自己扛:

  • 断点恢复 :消费者挂了,重启后得用 resumeToken(恢复令牌)接着上次的位置继续,这个 token 怎么持久化、怎么处理过期,全是你自己的活。
  • 目标端映射:Mongo 的文档结构落到关系型表里怎么对应、嵌套文档怎么拆、类型怎么转,自己写。
  • 消费监控:一个消费线程悄悄死了、没人发现,漏的数据能让你半夜惊醒。

所以这条路适合「有开发资源、需求比较定制」的场景;想省心的话,接着往下看。

四、路线三:同步工具(MongoShake / mongosync / 云 DTS)

不想自己写代码,那就找现成工具。搜「MongoDB 数据同步」大概率会碰到这几个名字,我按场景帮你划个边界:

  • MongoShake:阿里云开源的工具,本质也是读 oplog 做转发。强项是 Mongo → Mongo、Mongo → Kafka 这类链路,配置简单,社区活跃。适合「Mongo 生态内部」的实时同步。
  • mongosync:MongoDB 官方出的工具,定位是「集群间的一次性数据迁移」,适合搬一次就完事的那种,不适合当长期实时同步通道。
  • 云厂商 DTS :各家云自带的传输服务,开箱即用、有控制台。但缺点是绑死自家云------目标端要是自建库、私有化环境或者信创环境,基本用不上。

这三个的共同边界是:它们主要围着 MongoDB 生态打转,或者围着某朵云打转。 一旦你的目标端是「关系型库 / 数仓 / 国产库」,它们就有点使不上劲了。

五、路线四:异构 CDC 工具(MongoDB → 关系型/国产库)

前面三条路线,其实共享一个盲区:目标端不是 MongoDB 生态的时候,都差点意思。

  • Change Streams:要自己写映射、自己管断点;
  • MongoShake:到关系型库还得再接一层;
  • 云 DTS:绑云,出不了圈。

所以就有了第四条路线------专门的异构 CDC(Change Data Capture)工具。它的定位就是「在不同类型的数据库之间做增量同步」,把读日志、类型映射、断点续传、一致性校验这些脏活累活打包干掉。

我在信创项目里接触比较多的是电科金仓的 KFS(Kingbase FlySync)。它走的路子和 Oracle GoldenGate 是一个思路:挂在源库上解析日志(Redo Log、Binlog、MongoDB 的 oplog 这类),把变化实时捕获下来,再翻译成目标端能懂的语句写进去。官方口径里,它内置 20 余种源库适配器,MongoDB、Oracle、MySQL、SQL Server 都在里面,目标端可以落到 KingbaseES 这类关系型库。

几个我印象比较深的点,都是它跟「自己写 Change Streams」拉开差距的地方:

  • 秒级延迟:做的是日志级 CDC,不是定时轮询,增量同步延迟能压到秒级(官方标称 1 秒以内)。
  • 断点续传:任务中断了,按位点(LSN/SCN 那套思路)记着搬到哪了,恢复后接着跑,不用从头再来。
  • 在线数据比对:同步完还能帮你做数据比对,行数、内容对不上会告警,不用自己写脚本去数。

当然,客观说一句:这类商业化工具的代价是得部署管理端、要付费,比你自己写 Change Streams 成本高。所以它的适用场景也很明确------目标是关系型/国产库、要长期实时同步、又不想养一套同步代码。如果你的目标端就是另一个 MongoDB 集群,那路线三里更轻的选项可能更合适。

六、选型对比表

不想看长篇的,直接看这张表:

方案 同步方向 是否实时 维护成本 最适合的场景
副本集 oplog 复制 Mongo 集群内部(主↔从) 是(近实时) 低(MongoDB 自带) 高可用、读写分离、副本集扩容
Change Streams Mongo → Mongo / 应用 / 消息 高(自己写消费者) 有开发能力、需求定制
MongoShake / mongosync / 云 DTS 主要在 Mongo 生态内 是(MongoShake)/ 否(mongosync 一次性) Mongo→Mongo、Mongo→Kafka、一次性迁移
异构 CDC 工具(如金仓 KFS) Mongo → 关系型 / 数仓 / 国产库 中(商用工具) 目标端非 Mongo 生态、要长期实时同步

一句话总结选型逻辑:先看目标端是谁。 目标端还是 Mongo,走前三条;目标端是关系型/国产库,直接看第四条。

七、实操演练:给副本集加个从节点

光说不练假把式。这里补一个最常用的操作------给副本集扩容一个从节点,并观察它的同步进度。这也是官方文档往往一笔带过、但生产上总要手把手做的一步。

前提:新节点的 mongod 已经用和副本集相同的 keyfile 启动,配置文件里 replication.replSetName 一致。

在主节点上执行:

javascript 复制代码
rs.add("mongodb://新节点IP:27017")

加进去之后,新节点会进入 STARTUP2 状态开始初始同步。想看它同步到哪了,别只看 rs.status() 的状态,再开一个窗口看它的实际操作:

javascript 复制代码
// 在任意节点上,看正在跑的同步相关操作
db.currentOp({
  "desc": { $regex: "initial sync|repl" }
})

或者直接去新节点的日志里找 initial sync 关键字,能看到类似「synced from」和分阶段进度。判断同步完成的标准rs.status() 里该成员的 stateStrSTARTUP2 变成 SECONDARY,并且 rs.printSecondaryReplicationInfo() 里它的落后时间趋近于 0。

一个小提醒:初始同步会从源节点拉全量数据,多少会占源节点的 IO 和带宽,尽量避开业务高峰期做扩容;数据量大的话,先评估一下 oplog 够不够大,别同步到一半 oplog 窗口不够又得重来。

八、初始同步的两种姿势:逻辑同步 vs 文件拷贝

上面说的「初始同步」,从 MongoDB 5.2 版本起其实有两种玩法,很多人在这一步吃了亏,我单独拎出来说。

逻辑同步(默认) :一边从源节点扫描集合、复制文档,一边同步建索引;这期间新产生的写操作,先暂存在本机 local 库的 oplog 里,全量拷完再追增量。好处是通用、不用停任何东西;坏处是慢------数据量大、索引多的时候,光建索引就能磨很久。

文件拷贝同步 :直接把源节点的数据文件(WiredTiger 文件)拷过去。速度明显快,但限制一大堆,而且是 Enterprise 版本才有的

  • 替换掉目标节点的 local
  • 同步期间不能做备份
  • 不能并发从多个源同步;
  • 拷贝完 count() 结果可能不准确(文件快照的元数据和实际行数有出入)。

所以我的建议很粗暴:能走逻辑同步就走逻辑同步,除非数据量巨大、你又用的是企业版、而且清楚上面几个坑。文件拷贝那套,更像是「官方给超大集群开的后门」,不是给你图省事用的。

还有个参数值得记住:initialSyncSourceReadPreference。它决定新节点从哪个成员拉数据------默认优先主节点,主节点不合适再放宽条件筛一轮。生产上如果不想让主节点扛这个 IO,可以把它指到一个专用同步源(比如 hidden 节点,后面第十节会讲)。

小知识:初始同步是有容错的。遇到临时网络错误会自己恢复,默认给 24 小时恢复窗口、最多重试 10 次;过了窗口还没救回来,就换一个健康源从头再来。所以偶尔看到它「重来」,别慌,先看是不是网络抖了。

九、同步进度怎么盯、一致性怎么验

「同步完了」得有客观标准。我的习惯是几件套一起上:

① 看进度。 rs.status()stateStr 是粗颗粒的,再补一个更细的:

javascript 复制代码
db.currentOp({
  "desc": { $regex: "initial sync|repl|sync" }
})

新节点自己的日志里搜 initial sync,能看到分阶段进度(建索引、复制集合、追 oplog)。判断「追平」的金标准:rs.printSecondaryReplicationInfo() 里落后时间稳定在个位数毫秒,且不再扩大。

② 数行数。 全量同步完,源端和目标端分别跑一遍:

javascript 复制代码
db.order_info.countDocuments({})

行数对上了,才过第一关。

③ 抽数据比对。 行数一样不代表内容一样。Mongo 原生没有跨集群的「一键校验」,要么自己写脚本抽样比对,要么直接用同步工具自带的数据比对能力(这也是前面 KFS 那类工具的价值之一------把比对做成内置能力,对不上会告警)。自己写的话,挑几个关键字段做抽样哈希比对就够用。

④ 看延迟趋势。 生产上最好给复制延迟挂监控,落后时间超过阈值就告警。等肉眼发现数据不对,往往已经晚了。

十、几个容易忽略的特殊拓扑

前面讲的都是「规规矩矩的副本集」,生产上总有些特殊配置,同步行为会不一样,逐个点一下:

  • 链式复制(chained replication) :默认情况下,从节点可以从别的从节点拉 oplog,而不是必须连主节点。好处是给主节点减负;代价是一层链上的延迟会叠加。某个中间节点慢了,下游跟着慢,排查时要先搞清楚「谁在给谁供数据」。
  • 延迟节点(delayed secondary) :故意让某个从节点落后一段时间 (比如 1 小时)再应用 oplog。它不是为了同步快,而是当「后悔药」------误删了数据,还能从这个节点把 1 小时前的状态捞回来。这种节点的「落后」是人为的、正常的,别一看落后就报警。
  • hidden 节点(hidden member) :不参与选举、也不对外提供读服务,但照样同步。特别适合当备份源或同步源 ------把 initialSyncSourceReadPreference 指到 hidden 节点,加新节点时就能绕开主节点。
  • 分片集群:每个分片本质上是独立副本集,同步按分片各自进行。给某个分片扩容、重同步,只影响那一个分片,别把整个集群当成一个整体去操作。

十一、踩坑清单与速查

这些年我在「MongoDB 数据同步」这件事上栽过的跟头,挑几个高频的列出来:

  1. 从节点突然 stale :主节点 oplog 被覆盖,从节点追不上。判断信号:rs.printSecondaryReplicationInfo() 落后时间越来越大,或者日志报 too stale to catch up。解法:删数据重做初始同步(第七节那套),并同步把 oplog 调大。
  2. 「复制落后多少算 stale」心里没数:别等它报错才看。给复制延迟挂告警,落后时间超过业务容忍线(比如 30 秒)就触发,比事后救火强。
  3. Change Streams 的 resumeToken 丢了:消费者重启后拿不到 token,只能从当前时间点重放,中间变化就漏了。解法:把 token 落到持久化存储里,随消费进度一起更新。
  4. 同步完成后没校验:同步「跑完」不等于「跑对」。用工具自带的数据比对,或者自己写脚本抽几条对一对,别等业务发现数据少了再回头查。
  5. 拿 mongodump 给副本集成员做种子 :不行。副本集成员的种子要保证一致性快照、还要带 local 库,mongodump 满足不了这个前提,容易出「看着好了、实际不一致」的暗病。
  6. 忘了账号权限这茬 :读 oplog、订阅 Change Streams 都需要相应权限,账号没配好,工具会报 not authorized。源端给个能读 oplog、目标端给个能写的账号,别用 admin 一把梭。
  7. 低估了读 oplog 对源库的影响:无论自建消费者还是工具,都是靠持续读源端 oplog 干活。高频写入场景下,这个读取是有开销的,源端 oplog 保留时长、抓取频率都要调好,别把「同步」变成了「拖累」。

顺手附一张速查表,出问题先对着找:

现象 / 报错信号 大概原因 先查什么
too stale to catch up oplog 被覆盖,从节点落后太多 rs.printSecondaryReplicationInfo() 看落后时间
成员卡在 STARTUP2 初始同步没完成 / 反复重试 新节点日志搜 initial sync,看网络与源节点 IO
日志出现 OplogStartMissing 要找的 oplog 起点已被覆盖 重做初始同步,评估 oplog 大小
Change Streams 中断后连不上 resumeToken 失效 检查 token 持久化、oplog 窗口是否够
同步「跑完」但数据对不上 缺一致性校验 / 文件拷贝的 count 偏差 行数比对 + 抽样哈希比对
工具报 not authorized 账号权限不足 核对源端读 oplog、目标端写权限

十二、复盘与 Checklist

把这事想通之后,你会发现 MongoDB 数据同步没那么玄乎,难就难在「选对方案」。送你一份 Checklist,动手前先过一遍:

  • 目标端是 Mongo 还是关系型/数仓/国产库?(决定走内还是走外)
  • 要实时同步,还是一次性迁移?(决定长期通道还是搬一次)
  • 有没有开发资源维护自研消费者?(决定 Change Streams 还是工具)
  • 是否私有化/信创环境?(决定能不能用云 DTS)
  • 数据量大不大、要不要用文件拷贝同步?(决定初始同步姿势)
  • 有没有盯着复制延迟、oplog 窗口?(决定会不会半夜被叫醒)
  • 同步完成后有没有做一致性校验?(决定数据错不错)

MongoDB 数据同步这件事,一半功夫在选对路线,另一半在你有没有把延迟、断点和一致性这三件事提前想清楚。 这句话送给你,能省不少半夜救火的工夫。


我是DBA小马哥,十年一线数据库运维。写的东西都是生产环境里趟出来的,关注我,少踩坑。

相关推荐
香菜TTT3 小时前
Redis的哨兵机制
java·数据库·redis
风哥2号4 小时前
数据库教程FGMT13‑Oracle性能优化之数据仓库与分区表
数据库·oracle·性能优化
玉宇夕落4 小时前
Docker + Milvus,数据库永久存放记忆,让对话历史永不丢失
数据库·docker·llm
Zenova EdgeOS5 小时前
工业网关数据持久化:从同步到 WAL 的工程实战
网络·数据库·oracle
企查查数据服务5 小时前
全军禁入下客商准入风控,关联图谱与穿透核查
大数据·开发语言·数据库·php
冰帆<5 小时前
DBViewer — 把数据库工作台,安装进浏览器
数据库·数据可视化·数据同步
学Linux的语莫5 小时前
LangChain Prompts 提示词模板
数据库·人工智能·langchain
敲代码的嘎仔5 小时前
从集群锁失效到异步领券:我用分布式锁 + Redisson + MQ 把领券接口 RT 从 200ms 压到 15ms
java·数据库·redis·分布式·缓存·ai·wpf
步行cgn5 小时前
Spring 的模块体系详解
java·数据库·spring