当单台 MongoDB 服务器的存储或写入能力达到瓶颈时,需要使用分片(Sharding)进行水平扩展。分片将数据分布在多个服务器上,每个分片是一个独立的复制集。
1. 分片架构
Client
│
┌──────────┴──────────┐
│ mongos (路由) │
│ (可部署多个) │
└──────────┬──────────┘
│
┌──────────┴──────────┐
│ Config Servers │
│ (复制集,3节点) │
└──────────┬──────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼────┐ ┌─────▼────┐ ┌─────▼────┐
│ Shard A │ │ Shard B │ │ Shard C │
│(Replica) │ │(Replica) │ │(Replica) │
└──────────┘ └──────────┘ └──────────┘
2. 分片键选择
2.1 分片键类型
| 类型 | 特点 | 适用场景 |
|---|---|---|
| Range | 按范围分片 | 范围查询频繁(如时间) |
| Hashed | 哈希均匀分布 | 写密集型,避免热点 |
| Zone | 数据路由到指定区域 | 多数据中心,合规要求 |
2.2 分片键设计原则
// 好的分片键特征:
// 1. 高基数(cardinality)- 大量不同值
// 2. 均匀分布 - 避免热点
// 3. 查询频率高 - 常用查询条件
// ❌ 差的分片键:
db.orders.createIndex({ status: 1 }); // 只有几个值,不均匀
// ✅ 好的分片键:
db.orders.createIndex({ userId: 1, _id: 1 });
sh.shardCollection("mydb.orders", { userId: 1, _id: 1 });
// 以 userId 为主分片键(高基数),_id 作为细化
2.3 分片后查询路由
// 包含分片键的查询 → mongos 路由到指定分片(Targeted)
db.orders.find({ userId: "user123" });
// 不包含分片键的查询 → Scatter-Gather 到所有分片
// 性能下降,尽量避免
db.orders.find({ status: "completed" });
3. Chunk 与 Balancer
// Chunk 是分片内数据的逻辑单元(默认 64MB)
// Balancer 自动在分片间迁移 Chunk 以保持负载均衡
// 查看分片状态
sh.status();
// 手动移动 Chunk
sh.moveChunk("mydb.orders", { userId: "user123" }, "shard002");
// 设置 Chunk 大小
use config
db.settings.updateOne(
{ _id: "chunksize" },
{ $set: { value: 32 } }, // 32MB
{ upsert: true }
);
// 关闭 Balancer(维护时)
sh.stopBalancer();
sh.startBalancer();
4. Zone 策略
// 将特定范围的数据固定到指定分片(多数据中心场景)
sh.addShardToZone("shard000", "EU");
sh.addShardToZone("shard001", "US");
sh.updateZoneKeyRange(
"mydb.users",
{ region: "EU", _id: MinKey },
{ region: "EU", _id: MaxKey },
"EU"
);
// 欧洲用户数据存储在 Europe 数据中心
5. 分片扩容
1. 准备新分片服务器并启动 mongod
2. 将新分片加入集群: sh.addShard("shard4/rs0.example.com:27017")
3. Balancer 自动开始迁移 Chunk 到新分片
4. 监控迁移进度: sh.status()
6. 分片限制
| 功能 | 未分片 | 分片后 |
|---|---|---|
| $lookup | 支持 | 仅支持非分片集合 |
| 唯一索引 | 支持 | 必须包含分片键 |
| $text 搜索 | 支持 | 不支持 |
| 多文档事务 | 支持 | 4.2+ 支持,但性能受影响 |
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。