跳到主要内容

分片集群

分片(Sharding)是 MongoDB 实现水平扩展的机制,将数据分布到多个服务器上。

分片基础

什么是分片?

分片将数据集合分割成多个数据块(Chunk),分布在不同的分片节点上:

分片组件

组件说明
mongos路由服务器,客户端连接入口
shard分片节点,存储实际数据
config server配置服务器,存储集群元数据

分片优势

  1. 水平扩展:通过增加分片来扩展容量
  2. 负载均衡:分散查询和写入压力
  3. 高可用:单个分片故障不影响整体
  4. 地理分布:数据可以分布到不同地区

分片键选择

分片键概念

分片键(Shard Key)决定了数据如何分布:

// 集合使用 userId 作为分片键
// 数据分布
// userId: 0-999 → Shard 1
// userId: 1000-1999 → Shard 2
// userId: 2000-2999 → Shard 3

选择分片键的原则

  1. 高基数:分片键应有足够的唯一值
  2. 查询局部性:查询通常基于哪些字段
  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 })

小结

本章我们学习了:

  1. 分片基础:水平扩展原理和架构
  2. 分片组件:mongos、shard、config server
  3. 分片键选择:原则和策略
  4. 分片配置:集群搭建和集合分片
  5. 分片操作:数据分布和均衡器
  6. 分片查询:查询路由和优化
  7. 最佳实践:分片键选择和监控