分片集群
分片(Sharding)是 MongoDB 实现水平扩展的机制,将数据分布到多个服务器上。
分片基础
什么是分片?
分片将数据集合分割成多个数据块(Chunk),分布在不同的分片节点上:
分片组件
| 组件 | 说明 |
|---|---|
| mongos | 路由服务器,客户端连接入口 |
| shard | 分片节点,存储实际数据 |
| config server | 配置服务器,存储集群元数据 |
分片优势
- 水平扩展:通过增加分片来扩展容量
- 负载均衡:分散查询和写入压力
- 高可用:单个分片故障不影响整体
- 地理分布:数据可以分布到不同地区
分片键选择
分片键概念
分片键(Shard Key)决定了数据如何分布:
// 集合使用 userId 作为分片键
// 数据分布
// userId: 0-999 → Shard 1
// userId: 1000-1999 → Shard 2
// userId: 2000-2999 → Shard 3
选择分片键的原则
- 高基数:分片键应有足够的唯一值
- 查询局部性:查询通常基于哪些字段
- 避免单调递增:导致数据分布不均
// 不好的分片键(单调递增)
{ _id: 1 } // 新数据总写入最后一个分片
// 好的分片键
{ userId: 1 } // 用户数据均匀分布
{ orderDate: 1, orderId: 1 } // 按日期范围分布
分片键类型
// 基于哈希的分片(均匀分布)
sh.shardCollection("app.orders", { orderId: "hashed" })
// 基于范围的分片
sh.shardCollection("app.users", { region: 1, userId: 1 })
配置分片集群
启动分片组件
# 1. 启动配置服务器(3 个)
mongod --configsvr --port 27019 --dbpath /data/config --replSet configReplSet
# 2. 启动分片服务器(3 个)
mongod --shardsvr --port 27017 --dbpath /data/shard1
mongod --shardsvr --port 27018 --dbpath /data/shard2
mongod --shardsvr --port 27020 --dbpath /data/shard3
# 3. 启动 mongos 路由
mongos --configdb configReplSet/localhost:27019
初始化分片
// 连接到 mongos
// 添加分片
sh.addShard("localhost:27017")
sh.addShard("localhost:27018")
sh.addShard("localhost:27020")
// 启用数据库分片
sh.enableSharding("myapp")
// 对集合进行分片
sh.shardCollection("myapp.users", { userId: "hashed" })
sh.shardCollection("myapp.orders", { orderDate: 1 })
查看分片状态
// 查看集群状态
sh.status()
// 查看分片信息
db.getSiblingDB("config").shards.find()
// 查看chunk分布
db.getSiblingDB("config").chunks.find().pretty()
分片操作
数据分布
// 查看数据分布
db.getSiblingDB("admin").runCommand({ flushRouterConfig: "myapp.users" })
// 手动移动 chunk
db.getSiblingDB("admin").runCommand({
moveChunk: "myapp.users",
find: { userId: 5000 },
to: "shard0002"
})
均衡器
// 查看均衡器状态
sh.getBalancerState()
// 禁用均衡器(维护时使用)
sh.stopBalancer()
// 启用均衡器
sh.startBalancer()
// 设置均衡窗口
db.getSiblingDB("config").settings.update(
{ _id: "balancer" },
{ $set: { activeWindow: { start: "22:00", stop: "02:00" } } },
{ upsert: true }
)
分片查询
查询路由
// 包含分片键的查询(高效)
db.orders.find({ orderDate: ISODate("2024-01-15") })
// mongos 直接定位到特定分片
// 不包含分片键的查询(需要广播)
db.orders.find({ customerId: 123 })
// mongos 需要查询所有分片
投影和排序
// 使用索引排序(可以路由到单个分片)
db.orders.find({ orderDate: { $gt: new Date() } })
.sort({ orderDate: 1 })
// 不使用索引排序(需要聚合)
db.orders.find().sort({ total: -1 })
// 需要从所有分片获取数据后排序
分片最佳实践
1. 选择合适的分片键
// 场景:用户数据
// 推荐:使用 userId 或 email 作为分片键
sh.shardCollection("app.users", { userId: "hashed" })
// 场景:时间序列数据
// 推荐:使用时间字段作为分片键
sh.shardCollection("app.logs", { timestamp: 1 })
// 场景:复合查询
// 推荐:结合多个字段
sh.shardCollection("app.orders", { customerId: 1, orderId: 1 })
2. 避免热点数据
// 问题:使用时间戳分片导致写入热点
sh.shardCollection("app.events", { createdAt: 1 })
// 解决:使用哈希分片
sh.shardCollection("app.events", { eventId: "hashed" })
// 或使用复合键
sh.shardCollection("app.events", { date: 1, eventId: 1 })
3. 监控分片分布
// 查看 chunk 分布
db.getSiblingDB("config").chunks.groupBy("shard")
// 查看数据量分布
db.getSiblingDB("admin").runCommand({ dataSize: "myapp.users", scale: 1024 })
4. 预分片
// 创建集合时指定初始分片数量
db.runCommand({
shardCollection: "myapp.largecoll",
key: { userId: 1 },
numInitialChunks: 100
})
分片故障排除
// 查看分片状态
sh.status()
// 检查mongos配置
db.adminCommand({ getShardingInfo: 1 })
// 查看正在进行的操作
db.currentOp()
// 修复分片不一致
db.adminCommand({ repairDatabase: 1 })
小结
本章我们学习了:
- 分片基础:水平扩展原理和架构
- 分片组件:mongos、shard、config server
- 分片键选择:原则和策略
- 分片配置:集群搭建和集合分片
- 分片操作:数据分布和均衡器
- 分片查询:查询路由和优化
- 最佳实践:分片键选择和监控