01. 分片是什么、什么时候需要它
当单台服务器的磁盘、内存、CPU 撑不住数据量或并发量时,就需要分片——把数据分布到多台服务器上,每台只存一部分数据。
分片集群的三个组件:
shard——存储实际数据的副本集,每个分片是一个独立的副本集。
mongos——路由器,客户端连的是它,它根据分片键把请求路由到正确的分片。对客户端来说 mongos 就是完整的 MongoDB,感知不到分片。
config server——存储集群元数据(哪个分片存哪些数据),也是副本集。
什么时候需要分片?数据量超过单机磁盘容量、写入量超过单机 IO 能力、内存装不下热数据。但分片增加运维复杂度,能不分尽量不分。
bash
# 分片集群架构示意
# mongos (路由) ← 客户端连这里
# ↓
# config server replica set (元数据)
# ↓
# shard1 replica set + shard2 replica set + shard3 replica set
# (数据分片 1) (数据分片 2) (数据分片 3)02. 分片键——最重要的决策
分片键是决定数据怎么分布的核心。选分片键是分片集群里最重要的设计决策,选错了后面要改基本等于重建。
好的分片键满足三点:
1. 高基数——值足够多,能把数据均匀散开。比如 userId 基数高,gender(男/女)只有两个值不行。
2. 读写分布均匀——没有热点。比如用时间戳当分片键,所有新写入都集中到一个分片上,其他分片闲着。
3. 查询能精准路由——查询条件里包含分片键,mongos 就能直接把请求发到正确的分片,不用广播到所有分片。
常见分片键:散列分片(Hash Sharding)——对字段的哈希值分片,能保证均匀分布但范围查询会广播到所有分片。范围分片(Range Sharding)——按分片键的值范围分片,支持高效范围查询但可能有热点。
javascript
// 启用分片
sh.enableSharding("mydb");
// 散列分片(按 userId 哈希)
sh.shardCollection("mydb.orders", { user_id: "hashed" });
// 范围分片(按 created_at 范围)
sh.shardCollection("mydb.logs", { created_at: 1 });
// 复合分片键
sh.shardCollection("mydb.events", { user_id: 1, created_at: 1 });分片键选好后不能修改。如果选错了只能重建集合重新分片——这是一项巨大的工程。
03. Chunk 与均衡器
分片集群内部把数据按分片键范围切分成一个个 Chunk(块)。默认每个 Chunk 大小是 128MB。Chunk 是数据迁移的最小单元。
均衡器(Balancer)负责保持各分片之间的 Chunk 数量平衡。当某个分片的 Chunk 比别的多太多时,均衡器自动迁移 Chunk 到其他分片。
Chunk 分裂:当某个 Chunk 的数据增长超过 Chunk Size,会自动分裂成两个 Chunk。分裂是轻量操作(只改元数据),迁移才是重的。
均衡器在后头低优先级运行,不会影响正常的读写请求。但迁移期间涉及数据复制,如果集群负载已经很高可能导致性能波动。可以设置均衡器的运行窗口(比如只在凌晨跑)。
javascript
// 查看集群状态
sh.status();
// 查看均衡器状态
sh.isBalancerRunning();
// 设置均衡器运行窗口(凌晨 2-6 点)
sh.setBalancerState(true);
db.settings.updateOne(
{ _id: "balancer" },
{ $set: { activeWindow: { start: "02:00", stop: "06:00" } } }
);
// 手动迁移 Chunk
sh.moveChunk("mydb.orders", { user_id: ObjectId("...") }, "shard0002");sh.status() 输出很长但信息很全——每个分片多少 Chunk、数据量多大、均衡器是否在跑,一目了然。
04. 查询路由与性能考量
mongos 接收到查询后会根据分片键决定怎么处理:
目标查询——查询条件包含分片键,mongos 能精准路由到对应的分片,只查一个分片。这是最理想的情况。
广播查询——查询条件不包含分片键,mongos 把查询发给所有分片,然后合并结果。效率低得多而且结果排序可能不准。
聚合管道的分片:尽量把美元 match 和美元 sort 放在最前面让各分片本地执行,最后在 mongos 合并。能用 lookup、group 时注意数据是否跨分片——跨分片聚合需要大量数据传输。
排序的坑:分片集群里 sort 的结果是各分片先排序,mongos 再合并排序。如果数据量很大且没有分片键参与,合并排序可能成为瓶颈。
javascript
// 目标查询(好)
db.orders.find({ user_id: ObjectId("...") });
// mongos 直接路由到对应的分片
// 广播查询(不好)
db.orders.find({ amount: { $gt: 100 } });
// mongos 要把查询发给所有分片再合并结果
// 分片环境下的排序
db.orders.find({ user_id: ObjectId("...") }).sort({ created_at: -1 });
// 如果有分片键过滤,排序在单个分片内完成尽量在查询里包含分片键。没有分片键的查询叫 Scatter-Gather——分散到各分片再汇总,效率低。
05. 分片集群运维
分片集群比单机或副本集复杂得多,运维要点:
1. 监控 Chunk 分布——用 sh.status() 定期检查各分片的 Chunk 是否均匀。不均衡说明分片键选得不好或均衡器没在跑。
2. config server 备份——config server 存着集群所有元数据,它的备份优先级最高。config server 挂了整个集群就挂了。
3. 添加新分片——用 sh.addShard() 添加新副本集作为分片,均衡器会自动迁移部分 Chunk 到新分片。迁移过程可能很慢(大数据量可能要几天)。
4. 移除分片——用 db.adminCommand({ removeShard: "shardName" }) 把分片上的所有 Chunk 迁移到其他分片,然后移除。这个操作可能有损,确保所有数据迁移完再执行。
5. 版本升级——mongos、config server、shard 有自己的升级顺序,需要严格按文档来。
javascript
// 添加新分片
sh.addShard("myReplSet4/localhost:27040");
// 移除分片(先把数据迁走)
db.adminCommand({ removeShard: "myReplSet4" });
// 反复执行直到 drained: true
// 查看各分片的数据量
use config;
db.chunks.aggregate([
{ $group: { _id: "$shard", count: { $sum: 1 } } }
]);removeShard 操作不可逆。执行前确保所有数据已迁移完成(drained: true),确认无误后再最终移除。
知识测验
第 1/5 题正确 0
分片集群中 mongos 的角色是?