MongoDB分片集群通过水平拆分数据到多个Shard节点,实现海量数据的分布式存储和并行查询。当单节点存储容量或写入吞吐量达到瓶颈时,分片是直接有效的扩展方案。本文围绕分片集群架构搭建、片键选择、Balancer平衡器配置和分片运维,给出完整的实战方案。
MongoDB分片集群架构与组件角色
分片集群由三类节点组成。Shard存储实际数据,每个Shard是一个Replica Set。Config Server存储集群元数据(分片路由信息)。Mongos是查询路由器,接收客户端请求并路由到对应Shard。
架构示意:
Mongos Router (x3) -> Config Server RS (x3) -> Shard RS (x2-3)
生产环境最小配置:3个Config Server(Replica Set),2个以上Shard(每个为3节点Replica Set),2个以上Mongos。以下为3 Shard集群部署步骤:
分片集群搭建与Config Server配置
# 1. 启动Config Server Replica Set(3节点)
# 节点1: 192.168.1.10
mongod --configsvr --replSet config-rs --bind_ip 192.168.1.10 --port 27019 --dbpath /data/config --logpath /var/log/mongodb/config.log --fork
# 节点2: 192.168.1.11
mongod --configsvr --replSet config-rs --bind_ip 192.168.1.11 --port 27019 --dbpath /data/config --logpath /var/log/mongodb/config.log --fork
# 节点3: 192.168.1.12
mongod --configsvr --replSet config-rs --bind_ip 192.168.1.12 --port 27019 --dbpath /data/config --logpath /var/log/mongodb/config.log --fork
# 初始化Config Server Replica Set
mongosh --host 192.168.1.10 --port 27019
> rs.initiate({
_id: "config-rs",
configsvr: true,
members: [
{_id: 0, host: "192.168.1.10:27019"},
{_id: 1, host: "192.168.1.11:27019"},
{_id: 2, host: "192.168.1.12:27019"}
]
})
> rs.status()
# 2. 启动Shard Replica Set(每个Shard 3节点)
# Shard 1: 192.168.1.20, .21, .22
mongod --shardsvr --replSet shard1-rs --bind_ip 192.168.1.20 --port 27018 --dbpath /data/shard1 --logpath /var/log/mongodb/shard1.log --fork
mongosh --host 192.168.1.20 --port 27018
> rs.initiate({
_id: "shard1-rs",
members: [
{_id: 0, host: "192.168.1.20:27018"},
{_id: 1, host: "192.168.1.21:27018"},
{_id: 2, host: "192.168.1.22:27018", arbiterOnly: true}
]
})
# Shard 2、Shard 3 同理配置
# 3. 启动Mongos路由
mongos --configdb config-rs/192.168.1.10:27019,192.168.1.11:27019,192.168.1.12:27019 --bind_ip 0.0.0.0 --port 27017 --logpath /var/log/mongodb/mongos.log --fork
# 4. 在Mongos中添加Shard
mongosh --host 192.168.1.10 --port 27017
> sh.addShard("shard1-rs/192.168.1.20:27018,192.168.1.21:27018")
> sh.addShard("shard2-rs/192.168.1.30:27018,192.168.1.31:27018")
> sh.addShard("shard3-rs/192.168.1.40:27018,192.168.1.41:27018")
# 5. 查看集群状态
> sh.status()
片键选择策略与分片集合配置
片键(Shard Key)决定数据在Shard间的分布方式,是分片设计中最关键的决策。片键一旦设定不可更改(MongoDB 4.4+支持resharding但成本高),需要在架构设计阶段慎重选择。
| 片键类型 | 特点 | 适用场景 | 风险 |
|---|---|---|---|
| 范围片键 | 按值范围分片,支持范围查询 | 时间序列、递增ID | 热点写入集中在最后一个Shard |
| 哈希片键 | 哈希值均匀分布,写入分散 | 等值查询为主 | 不支持范围查询优化 |
| 复合片键 | 多字段组合,兼顾范围和分散 | 业务查询多维度 | 字段顺序影响查询性能 |
// 启用数据库分片
> sh.enableSharding("ecommerce")
// 场景1:哈希分片(均匀写入,适合高并发写入场景)
> sh.shardCollection("ecommerce.orders", { "orderId": "hashed" })
// 查看分片分布
> db.orders.getShardDistribution()
// 场景2:复合片键(范围+哈希,兼顾范围查询和写入分散)
// 第一字段范围查询,第二字段哈希分散
> sh.shardCollection("ecommerce.events", { "createTime": 1, "userId": "hashed" })
// 场景3:范围分片(时间序列数据,按月分片)
> sh.shardCollection("ecommerce.logs", { "timestamp": 1 })
// 设置Chunk大小(默认64MB,可根据数据特征调整)
> db.adminCommand({ setParameter: 1, chunkSizeMB: 32 })
// 预分割Chunk(避免初期写入热点)
// 对哈希分片预先分割
> sh.splitAt("ecommerce.orders", { "orderId": NumberLong("4611686018427387904") })
> sh.splitAt("ecommerce.orders", { "orderId": NumberLong("9223372036854775808") })
> sh.splitAt("ecommerce.orders", { "orderId": NumberLong("13835058055282163712") })
片键选择的三个原则:高频查询字段必须在片键中或其前缀中,保证查询能路由到少量Shard;写入字段值应分散均匀,避免热点Shard;片键值不可变,文档的片键字段一旦写入不应修改。
Balancer平衡器配置与数据迁移
Balancer是Mongos后台进程,自动在Shard间迁移Chunk以保持数据均衡。默认开启,当Shard间Chunk数量差距超过阈值时触发迁移。
// 查看Balancer状态
> sh.getBalancerState()
> sh.isBalancerRunning()
// 查看Balancer窗口配置
> db.settings.find({ _id: "balancer" })
// 配置Balancer运行窗口(避免业务高峰期迁移影响性能)
> db.settings.update(
{ _id: "balancer" },
{
$set: {
activeWindow: {
start: "23:00", // 晚上23点开始
stop: "06:00" // 早上6点停止
}
}
},
{ upsert: true }
)
// 手动控制Balancer
> sh.stopBalancer() // 停止Balancer
> sh.startBalancer() // 启动Balancer
> sh.startBalancer(600) // 启动并指定超时(秒)
// 查看迁移状态
> db.currentOp({ "command.$truncated": { $exists: true } })
// 手动迁移Chunk(Balancer无法自动处理时)
> sh.moveChunk("ecommerce.orders",
{ "orderId": NumberLong("4611686018427387904") },
"shard3-rs"
)
// 设置Shard标签(将特定数据固定到特定Shard)
// 如将华东用户数据固定到上海机房的Shard
> sh.addShardTag("shard1-rs", "SH")
> sh.addShardTag("shard2-rs", "BJ")
> sh.addTagRange("ecommerce.users", { "region": "east" }, { "region": "eastz" }, "SH")
> sh.addTagRange("ecommerce.users", { "region": "north" }, { "region": "northz" }, "BJ")
Chunk迁移过程对业务的影响:MongoDB在迁移期间保持源Shard可读写,目标Shard先复制数据,完成后切换元数据路由,整个过程对应用透明。但迁移会消耗源和目标Shard的IO和带宽,大Chunk迁移可能持续数分钟。生产环境务必配置Balancer窗口在低峰期运行。
分片集群监控与运维
// 关键监控指标
// 1. 各Shard数据量分布
> db.getSiblingDB("config").chunks.aggregate([
{ $group: { _id: "$shard", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
// 2. 迁移历史
> db.getSiblingDB("config").changelog.find({
time: { $gt: new Date(Date.now() - 86400000) } // 最近24小时
}).sort({ time: -1 }).limit(20)
// 3. 各Shard存储大小
> db.adminCommand({ shardConnPoolStats: 1 })
// 4. 检查Orphaned Document(迁移残留)
> db.getSiblingDB("config").chunks.find({ ns: "ecommerce.orders" })
// 5. 查看集合分片状态
> sh.status({ verbose: 1 })
常见运维操作——添加新Shard扩容:
// 1. 部署新Shard Replica Set(与已有Shard配置一致)
mongod --shardsvr --replSet shard4-rs --bind_ip 192.168.1.50 --port 27018 --dbpath /data/shard4 --logpath /var/log/mongodb/shard4.log --fork
// 2. 在Mongos中添加Shard
mongosh --host 192.168.1.10 --port 27017
> sh.addShard("shard4-rs/192.168.1.50:27018,192.168.1.51:27018")
// 3. Balancer自动开始向新Shard迁移Chunk
// 观察迁移进度
> sh.isBalancerRunning()
> db.getSiblingDB("config").chunks.aggregate([
{ $group: { _id: "$shard", count: { $sum: 1 } } }
])
// 迁移直到各Shard Chunk数量趋于均衡
// 移除Shard(需要先迁移数据)
> db.adminCommand({ removeShard: "shard4-rs" })
// 返回 { msg: "draining started", state: "ongoing" }
// 持续查看进度
> db.adminCommand({ removeShard: "shard4-rs" })
// 直到返回 state: "completed",表示所有Chunk已迁移完毕
分片集群的设计决策需要在架构初期确定。选择哈希片键还是范围片键取决于查询模式:等值查询为主选哈希片键获得均匀分布,范围查询为主选范围片键获得查询优化。Balancer窗口配置避免迁移影响在线业务。添加Shard扩容时,数据迁移过程可能持续数小时到数天,取决于数据总量和网络带宽,需要在维护窗口内规划。分片不是银弹——单Replica Set能承受的负载优先用垂直扩展(更强CPU、更多内存、SSD),到达瓶颈后再考虑水平分片。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mongodb-fen-pian-ji-qun-bu-shu-shi-zhan-shard-fen-pian-yu/