1. 为什么你的分片集群会"偏科"?
当我们在MongoDB分片集群中突然发现某个分片服务器的磁盘使用率飙升到90%,而其他节点还有大量空闲空间时,就像班级里有个学生突然吃掉了整个披萨,而其他同学只能干瞪眼。这种数据分布不均的常见原因包括:
- 分片键选择不当:比如使用时间戳作为分片键,导致所有新数据都写入最后一个分片
- 热点数据集中:某个数值范围的数据被频繁访问(如特定用户ID段)
- 数据删除后遗留空洞:大量删除操作导致chunk碎片化
- 新增分片后的适应期:新加入的分片需要时间接收迁移的数据
最近我们生产环境就遇到一个典型案例:使用{userId:1}
作为分片键的用户表,由于某个广告活动导致特定用户段(50000-60000)的数据量暴增10倍,造成该分片负载激增。
2. 三步诊断法:找出不平衡的元凶
2.1 快速检查命令
# 连接mongos节点
mongo --host mongos01:27017
# 查看分片状态
sh.status()
# 检查均衡器状态
use config
db.locks.findOne({_id: "balancer"})
# 查询chunk分布
db.chunks.find({ns: "mydb.users"}).sort({shard:1})
2.2 C#监控示例(使用MongoDB.Driver)
using MongoDB.Driver;
var client = new MongoClient("mongodb://mongos01:27017");
var configDb = client.GetDatabase("config");
// 获取各分片chunk数量
var chunks = configDb.GetCollection<BsonDocument>("chunks");
var pipeline = new BsonDocument[]
{
new BsonDocument("$group",
new BsonDocument{
{"_id", "$shard"},
{"count", new BsonDocument("$sum", 1)}
})
};
var result = chunks.Aggregate<BsonDocument>(pipeline).ToList();
foreach(var doc in result)
{
Console.WriteLine($"分片 {doc["_id"]} 包含 {doc["count"]} 个chunk");
}
2.3 关键指标阈值
- 单个分片chunk数量超过其他分片2倍
- 最大分片数据量超过最小分片50%
- 分片间磁盘使用率差异超过30%
3. 平衡手术:让数据流动起来
3.1 自动平衡模式
# 启用平衡器(默认已开启)
sh.startBalancer()
# 设置平衡时间窗口(避免高峰时段)
use config
db.settings.update(
{ _id: "balancer" },
{ $set: { activeWindow : { start : "23:00", stop : "6:00" } } },
{ upsert: true }
)
3.2 手动干预六式
当自动平衡不够时,试试这些招数:
第一式:增大chunk尺寸
# 临时修改chunk大小为128MB
use config
db.settings.save({ _id: "chunksize", value: 128 })
# 永久修改需配置文件中设置
mongos --chunkSize 128
第二式:手动分裂大chunk
sh.splitAt("mydb.users", {userId: 30000})
sh.splitFind("mydb.users").min({userId: 1}).max({userId: 100000})
第三式:定向迁移chunk
sh.moveChunk("mydb.users", {userId: 25000}, "shard2")
第四式:紧急刹车
sh.stopBalancer()
sh.setBalancerState(false)
第五式:分片标签管理
sh.addShardTag("shard2", "hot")
sh.addTagRange("mydb.users", {userId: 0}, {userId: 100000}, "hot")
第六式:终极武器——清空重置
# 先排空分片数据
db.adminCommand({ removeShard: "shard3" })
# 数据迁移完成后重新加入集群
sh.addShard("shard3.example.net:27017")
4. 实时监控的三大法宝
4.1 内置工具三板斧
# 实时迁移监控
db.currentOp(true).inprog.forEach(
function(op) {
if(op.desc.indexOf("RangeDeleter") != -1) printjson(op)
}
)
# 性能分析
db.adminCommand({ getLog: "global" })
# 慢查询检测
db.setProfilingLevel(1, { slowms: 50 })
4.2 C#监控脚本示例
using MongoDB.Driver;
var client = new MongoClient("mongodb://mongos01:27017");
var adminDb = client.GetDatabase("admin");
// 获取迁移任务队列
var balancerStatus = adminDb.RunCommand<BsonDocument>(
new BsonDocument { { "balancerStatus", 1 } });
Console.WriteLine($"当前迁移队列长度: {balancerStatus["numQueuedMigrations"]}");
Console.WriteLine($"最近错误: {balancerStatus["lastError"] ?? "无"}");
// 检查分片容量差异
var shards = client.GetDatabase("config").GetCollection<BsonDocument>("shards");
foreach(var shard in shards.Find(new BsonDocument()).ToList())
{
var status = adminDb.RunCommand<BsonDocument>(
new BsonDocument { { "dbStats", 1 }, { "scale", 1024*1024*1024 } });
Console.WriteLine($"分片 {shard["_id"]} 数据量: {status["dataSize"]:N2} GB");
}
5. 什么情况下需要手动平衡?
- 业务高峰前准备:如电商平台在双11前预平衡
- 分片硬件升级后:新SSD分片需要快速填充数据
- 热点事件处理:突发新闻导致特定数据暴增
- 分片键变更过渡期:新旧分片键交替阶段
6. 技术选择的双刃剑
优点
- 自动平衡机制简单易用
- 手动控制提供应急手段
- 标签分片实现精细控制
缺点
- 平衡操作带来额外IO压力
- 超大chunk可能导致临时性能下降
- 手动操作存在误操作风险
7. 必须牢记的三大纪律
- 备份先行:操作前务必执行
mongodump
- 避开高峰:选择业务低峰期执行迁移
- 渐进调整:每次调整chunk大小不超过2倍
8. 总结与经验分享
经过多次实战,我们总结出分片平衡的"三三法则":
- 三次监控:操作前、中、后全程监控
- 三不原则:不盲动、不贪快、不侥幸
- 三备方案:回滚计划、熔断机制、应急手册
记住,良好的分片设计比后期平衡更重要。就像盖房子,地基打好了,后期装修才省心。下次设计分片方案时,不妨多花点时间分析数据分布模式,选择一个能自然分散数据的分片键,这比任何平衡技巧都管用。