哎,搬服务器这事儿,就像搬家。你肯定不希望箱子扔了、碗碎了,或者更惨的是,发现新房子钥匙丢了,旧房子也进不去了。MongoDB的数据迁移,很多人第一反应是mongodump和mongorestore,也就是“导出再导入”。听着挺简单,对吧?但如果你的数据库有好几个TB,或者业务不能停,这套老办法就是个噩梦。停服时间太长,用户骂声一片;导太久,怕半夜断电。
今天咱们不聊那些枯燥的文档,我就当是你的老司机,带你走一条真正“丝滑”的迁移路。我们的目标很明确:数据不丢,业务几乎无感知,切换在秒级完成。
第一步:别急着动,先看清楚你的“家当”
在动手之前,你得知道旧服务器上到底有啥。MongoDB的部署形态千差万别,是单机?副本集?还是分片集群?这决定了你后面走哪条路。
假设你最常见的情况:生产环境是MongoDB副本集(Replica Set)。这是大多数中大型应用的标准配置。如果你的单机版,后面我会单独说。
先登录旧服务器,检查一下副本集的状态:
mongosh --eval "db.adminCommand({ replSetGetStatus : 1 })"
你要记住几个关键信息:
- 各节点的角色:谁是Primary,谁是Secondary?
- 数据量:
db.stats()看看数据有多大。 - 网络延迟:新服务器和旧服务器在不在同一个机房?内网延迟是多少?这直接影响同步速度。
这里有个坑:很多人以为副本集的所有节点数据都一样,所以随便挑一个导。记住,永远只从Primary导出数据或做主从切换,除非你有明确的Secondary只读需求。
第二步:搭建新的“家”——新副本集部署
别急着导数据,先把新服务器搭建好。你要在新服务器上部署一个全新的MongoDB副本集。
2.1 安装与配置
确保新服务器的MongoDB版本不低于旧服务器版本。比如旧的是5.0,新的至少5.0,最好是6.0或7.0。版本倒着升,某些新特性可能会导致兼容性问题,别踩雷。
编辑新服务器的配置文件 /etc/mongod.conf,重点改这几项:
storage:
dbPath: /var/lib/mongodb # 确保磁盘空间足够
journal:
enabled: true
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: 27017
bindIp: 0.0.0.0 # 生产环境建议限制IP,但测试时可以宽泛点
replication:
replSetName: "rsNew" # 注意:副本集名字要和旧的区分开,避免混淆
启动MongoDB,然后初始化副本集:
mongosh
rs.initiate({
_id: "rsNew",
members: [
{ _id: 0, host: "新服务器IP1:27017" },
{ _id: 1, host: "新服务器IP2:27017" },
{ _id: 2, host: "新服务器IP3:27017" }
]
})
等等,为什么要搭多个节点? 因为我们要讲“秒级切换”。如果只有一个新节点,切换过程中一旦出错,你连回滚的余地都没有。副本集是迁移的基石,它能提供自动故障转移和数据复制能力。
2.2 关键配置:允许远程复制
新副本集搭好了,但旧服务器的数据怎么进去?这时候需要开启跨数据中心复制或者远程复制的功能。
在旧服务器的MongoDB配置中,确保开启了远程复制权限(默认是开的,但检查一下保险):
replication:
replSetName: "rsOld" # 旧的副本集名字
同时,新服务器需要能访问旧服务器的27017端口。如果是内网,直接连通;如果是公网,务必配置防火墙和白名单,只允许新服务器的IP访问旧服务器。
第三步:把旧房子“复印”到新房子——初始同步
这是最耗时的一步,也是很多人觉得“慢”的地方。但别慌,我们有技巧。
3.1 使用 rs.add() 将新节点加入旧副本集(推荐方案)
这是最骚的操作,也是实现“秒级切换”的核心。
与其导数据,不如让新服务器直接成为旧副本集的一个Secondary节点!
想象一下:你家里装修,直接把一张新的床搬进去,让它在旧房子里先待着,等旧房子拆了,你直接在新房子里睡觉,连被子都没换。
操作步骤:
- 登录旧服务器的Primary节点。
- 执行命令,将新服务器的IP加入副本集:
rs.add({ host: "新服务器IP:27017", priority: 0, votes: 0 })
为什么要设置 priority: 0 和 votes: 0?
priority: 0:防止新节点在同步过程中意外变成Primary,干扰生产。votes: 0:让它没有投票权,避免在旧副本集选举时产生混乱。
- 此时,新服务器会自动开始从旧服务器同步数据。这个过程叫Initial Sync。
监控同步进度:
在新服务器上运行:
mongosh --eval "db.adminCommand({ replSetGetStatus : 1 }).members.forEach(m => print(m.name + ' ' + m.stateStr + ' ' + m.optimeDate))"
你会看到新节点的状态是 ROLLBACK 或 RECOVERING,最后变成 SECONDARY。
这里有个重要的时间节点计算: 如果数据量是1TB,内网带宽是1Gbps,理论同步时间大约是: 1024 GB * 8 bits/byte / 1Gbps ≈ 8192 秒 ≈ 2.3小时。
如果你的数据很大,比如50TB,那这个办法就不行了。这时候需要用备份恢复法(后面会讲)。但对于大多数几TB以内的数据,直接加节点是最稳的。
3.2 如果数据量超大:备份恢复法
当数据量大到无法忍受网络同步时,可以用mongodump。但注意,不要用它来在线同步,只用来做初始数据的“快递”。
- 在旧服务器暂停写入(或者接受短暂不可写),然后dump:
mongodump --host localhost --port 27017 --out /backup/mongodb_backup
- 将备份文件
rsync到新服务器:
rsync -avz /backup/mongodb_backup/ 新服务器IP:/backup/mongodb_backup/
- 在新服务器上restore:
mongorestore --host localhost --port 27017 /backup/mongodb_backup
- 初始化新副本集,并配置为独立的副本集(不是加入旧的)。
但是,这种方法切换时会有一次明显的停机,不符合“秒级”要求。所以,超大容量迁移通常还是建议用方案一,哪怕多花点时间等同步,最后切换时才能做到无感。
第四步:让新旧房子“连通”——双向同步(可选,为了安全)
为了防止在同步期间,旧服务器有新写入,导致新服务器数据滞后,我们可以开启跨副本集复制(Cross-Cluster Replication, CCR),但MongoDB原生CCF比较高级(需要Atlas),开源版通常用应用层双写或者逻辑复制工具(如MongoDB Ops Manager的Replica Set Pairing)。
不过,对于大多数开发者,一个简单的逻辑复制方案更实用:
- 确保旧服务器的
oplog足够大,能覆盖从开始同步到切换的时间窗口。检查oplog大小:
db.printReplicationInfo()
如果oplog很小,比如只有几小时,而你同步需要一天,那数据肯定不全。这时候需要扩展oplog:
// 假设你要扩展到24小时
db.adminCommand({ resizeOplog: 1, size: 10240 }) // 10GB,单位MB
- 在同步期间,停止应用写入?不,我们不想停。 如果业务允许短暂只读,最好在凌晨低峰期做一次全量同步,然后让它慢慢追。 如果业务不能停,可以考虑在应用层做双写(同时写旧和新),但这会改代码,风险高。
更聪明的做法:利用MongoDB的Change Streams或者日志挖掘工具(如Debezium)来捕获旧库的增量变更,同步到新库。但这需要额外部署中间件。
对于大多数“不想折腾”的场景:我们选择在低峰期进行最终同步,然后快速切换。
第五步:秒级切换——最关键的时刻
数据同步完成了,新服务器已经是旧副本集的一个Secondary,并且数据基本一致。现在,我们要把它变成Primary,并切走流量。
5.1 提升新节点优先级
在旧服务器的Primary上执行:
// 查看当前配置
var cfg = rs.conf()
// 找到新节点的_id,假设是2
cfg.members[2].priority = 1 // 设为最高优先级
cfg.members[2].votes = 1 // 恢复投票权
cfg.members[0].priority = 0 // 把旧Primary降级
cfg.members[0].votes = 0
rs.reconfig(cfg)
会发生什么? MongoDB副本集检测到新节点优先级更高,会触发主节点选举。几秒钟内,新服务器就会变成Primary,旧服务器变成Secondary。
注意:在这个过程中,会有几秒到十几秒的不可用,因为选举需要时间,而且可能有短暂的写阻塞。
5.2 切换流量
如果你们的DNS TTL设置得很短(比如60秒),这时候可以切换DNS解析,让应用连接新IP。
但如果想要真正的秒级无感切换,建议用应用层路由或VIP(虚拟IP)。
VIP方案(推荐): 在新服务器上绑定一个VIP,或者使用Keepalived。在切换前,VIP指向旧Primary;切换后,VIP指向新Primary。应用配置连接VIP,完全无感知。
应用层方案:
修改应用的MongoDB连接字符串,从旧的mongodb://old-ip:27017改为mongodb://new-ip:27017。
最佳实践:双连切换 在切换瞬间,应用可以同时连接新旧两个集群,但只读新集群,写旧集群(需要双写支持)。或者,在代码层面做一个开关,瞬间切换到新的连接串。
5.3 验证切换
切换完成后,立即在新服务器上执行:
rs.status()
确认新节点状态是PRIMARY。
然后,在应用层面跑一个冒烟测试,确保读写正常。
第六步:处理单机版MongoDB
如果你用的是单机版,没有副本集,那迁移会麻烦一些。
方案:单机转副本集
- 在新服务器部署单机MongoDB。
- 将旧服务器数据
mongodump并mongorestore到新服务器。 - 停止旧服务器。
- 启动新服务器,并确保数据完整。
- 切换流量。
更高级的方案:单机转副本集
- 将旧单机MongoDB转换为单节点副本集(
rs.initiate())。 - 然后按照前面的步骤,添加新节点。
- 这样就能利用副本集机制进行同步和切换。
第七步:回滚预案——如果搞砸了怎么办?
迁移最怕的不是失败,而是失败了没法回头。
一定要保留旧服务器的运行状态!
- 在切换前,不要停止旧服务器的MongoDB服务。
- 将旧服务器设置为
priority: 0,让它只读。 - 如果新服务器出问题,立即将旧服务器提升为Primary:
var cfg = rs.conf()
cfg.members[0].priority = 1
cfg.members[0].votes = 1
rs.reconfig(cfg)
rs.stepDown() // 强制当前Primary下台
- 同时,将流量切回旧VIP或DNS。
记住:旧服务器是你最后的救命稻草,在它完全确认新系统稳定运行至少一周前,不要删除任何数据。
第八步:一些你可能没想到的细节
8.1 索引问题
有时候,数据同步完了,但索引还没建完。在新服务器刚变成Primary时,MongoDB可能会在后台重建索引,导致性能抖动。
建议:在同步期间,就在新服务器上预建好索引(如果知道有哪些索引的话),或者在切换后,密切监控db.currentOp(),看是否有后台索引构建任务。
8.2 认证与权限
确保新服务器的用户、角色、权限与旧服务器完全一致。可以用:
mongodump --auth -u admin -p password --db admin --collection system.users --out /backup/admin
然后在新服务器上restore admin库。
8.3 时区与字符集
检查新旧服务器的时区设置(timedatectl)和MongoDB的字符集配置(utf8)。不一致可能导致数据乱码或时间错误。
总结:一张图看懂流程
[旧服务器] --(rs.add)--> [新服务器]
| |
| (数据自动同步) | (等待同步)
| |
| | (提升优先级)
| v
| [新服务器成为Primary]
| |
| | (切换VIP/DNS)
| v
[应用] <------------------ [新服务器]
|
| (旧服务器保持Ready,随时回滚)
v
[旧服务器] (降级为Secondary,或离线备用)
最后的话
迁移MongoDB,听起来吓人,其实只要逻辑清晰,每一步都可控。核心秘诀就是:利用副本集机制,让数据自己跑过去,而不是你搬过去。
- 小数据量(<1TB):直接
rs.add()新节点,等同步,切主。 - 大数据量(>1TB):考虑备份恢复,或者扩容oplog后
rs.add()。 - 单机版:先转副本集,再迁移。
- 永远保留回滚方案。
别怕测试,先在测试环境演练一遍,把切换脚本写好,把监控配上。当你真正站在生产环境前,深呼吸,执行命令,看着数据平滑过渡,那感觉,就像变魔术一样。
祝你迁移顺利,半夜睡得安稳!如果有具体问题,随时再问。
