mongodb分片集群中balancer未自动迁移的主因是活动窗口(activewindow)限制,而非状态异常;需用db.admincommand或db.settings.update设置24小时制窗口(如"02:00"–"06:00"),并确保sh.setbalancerstate(true),窗口外仅禁止新迁移,不中断进行中任务。

直接停 Balancer 不是解法,它只会让不均更严重;真正要做的,是让迁移只在业务低谷跑,且避开 chunk 分布已失衡的“坏时机”。
为什么 sh.status() 显示 chunks 不均,但 Balancer 没动
常见现象是 sh.status() 里看到某 shard chunk 数比其他高一倍以上,但 sh.isBalancerRunning() 返回 false,sh.getBalancerState() 却是 true。这不是 Balancer 坏了,而是它被活动窗口(activeWindow)锁住了——当前时间不在设定的窗口内,哪怕倾斜再严重,它也不会触发任何新迁移。
- 窗口必须用 24 小时制字符串,如
"start":"02:00","stop":"06:00",不能跨天("23:00"到"01:00"会报错) - 窗口外即使有数据倾斜,Balancer 也不会调度新任务;但已在跑的迁移不会被中断
- 检查窗口是否生效:执行
sh.status(),输出中balancer:行下方应显示类似activeWindow: { start: "02:00", stop: "06:00" }
设置 activeWindow 的两种命令写法,别混用
不同 MongoDB 版本对设置方式敏感,用错会导致 CommandNotFound 或静默失败:
- MongoDB 4.2+(含云数据库 MongoDB 版主流版本):用
db.adminCommand({"setBalancerActiveWindow": 1, start : "02:00", stop : "06:00"}) - MongoDB 4.0 及更早(或部分旧版云实例):改 config 库 settings 集合:
db.settings.update({ _id: "balancer" }, { $set: { activeWindow: { start: "02:00", stop: "06:00" } } }, { upsert: true }) - 执行前必须确认 Balancer 已开启:
sh.setBalancerState(true),否则设置无效 - 权限注意:某些云环境要求用
rwuser执行,普通用户可能报CommandNotFound,此时需升级小版本或提工单
窗口设好了,为啥迁移还是卡在高峰期
根本原因是 activeWindow 只控制“新迁移任务”的调度时机,不干预已排队或正在执行的迁移。如果窗口开始前已有大量迁移积压,它们会在窗口开启后集中爆发,照样打满 CPU。
- 查是否有积压任务:
db.getSiblingDB("config").getCollection("migrationTasks").find().count(),非零值说明有残留任务 - 查是否真在跑:
sh.isBalancerRunning()返回true时,迁移正在进行,此时关 Balancer 无效,只能等它完成 - 观察迁移状态:
db.getSiblingDB("config").migrations.find({ "state": { $in: ["catching_up", "cloning", "committing"] } }).count(),长期卡在catching_up需人工 kill(用db.currentOp()找对应moveChunk操作) - chunkSize 是底层杠杆:默认 64MB,若设得过大(如 256MB),单次迁移耗时长、网络压力大,更容易撞上窗口结束时间点
告警持续出现时,先别调 Balancer,先看数据分布本质
chunks 数量不均 ≠ 实际数据不均。尤其在 MongoDB 6.0+,Balancer 已转向按集合数据大小(而非 chunk 数)判断均衡,sh.status() 默认输出容易误导。
- 运行
sh.status({verbose: true}),看是否有docs和size字段;没有就说明该版本不支持采样统计 - 直连分片查真实数据量:
mongo --host shard0001:27018→use db→db.coll.stats({scale: 1024*1024}),对比各分片的size和count - 若发现某 shard
size远超其他,但chunks并不多,说明存在 Jumbo Chunk 或写入倾斜,这时光调窗口没用,得手动sh.splitAt()分裂大 chunk
真正难处理的不是窗口怎么设,而是当 Balancer 被窗口约束后,你得自己盯住 chunk 分布拐点——比如凌晨 02:00 窗口一开,立刻用 sh.status() 看迁移是否启动、有没有卡住、目标分片磁盘是否突增。这些细节不写进监控脚本,光靠人工响应永远慢半拍。











