MongoDB分片集群通过水平拆分数据实现大规模存储和高吞吐写入能力。分片集群由三个组件构成:mongos路由节点、config server配置服务器和shard分片节点。每个分片本身是一个副本集,提供数据冗余和高可用。当单机存储容量或写入性能遇到瓶颈时,分片是MongoDB横向扩展的核心方案。
MongoDB副本集架构与选举机制
副本集(Replica Set)是MongoDB高可用的基础。一个副本集包含一个Primary节点和多个Secondary节点,通过Raft变体协议实现自动选举和故障转移。写入操作只发生在Primary,异步复制到Secondary。
# 部署3节点副本集(1 Primary + 2 Secondary)
# 节点1: 10.0.1.10:27017
# 节点2: 10.0.1.11:27017
# 节点3: 10.0.1.12:27017
# 每个节点的mongod.conf配置
# /etc/mongod.conf
net:
port: 27017
bindIp: 0.0.0.0
replication:
replSetName: rs0
# 在节点1上初始化副本集
mongosh --host 10.0.1.10:27017
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "10.0.1.10:27017", priority: 2 },
{ _id: 1, host: "10.0.1.11:27017", priority: 1 },
{ _id: 2, host: "10.0.1.12:27017", priority: 1 }
]
})
# 查看副本集状态
rs.status()
# 关注 "stateStr" 字段: PRIMARY / SECONDARY
# 关注 "health": 1 表示正常
选举机制中,priority值最高的节点优先成为Primary。当Primary故障时,剩余节点在electionTimeoutMillis(默认10秒)后触发选举,获得多数投票的Secondary提升为新的Primary。3节点副本集允许1个节点故障,5节点副本集允许2个节点故障。为避免脑裂问题,副本集节点数应为奇数。
Config Server与mongos路由节点部署
分片集群的Config Server存储集群元数据(分片信息、chunk范围等),mongos是无状态路由代理,接收客户端请求并路由到对应分片。Config Server本身也需要部署为副本集:
# 部署Config Server副本集(3节点)
# /etc/mongod.conf (config server)
sharding:
clusterRole: configsvr
replication:
replSetName: configReplSet
net:
port: 27019
# 初始化Config Server副本集
mongosh --host 10.0.1.20:27019
rs.initiate({
_id: "configReplSet",
configsvr: true,
members: [
{ _id: 0, host: "10.0.1.20:27019" },
{ _id: 1, host: "10.0.1.21:27019" },
{ _id: 2, host: "10.0.1.22:27019" }
]
})
# 部署mongos路由节点
# mongos.conf
sharding:
configDB: configReplSet/10.0.1.20:27019,10.0.1.21:27019,10.0.1.22:27019
net:
port: 27017
# 启动mongos
mongos -f /etc/mongos.conf
# 添加分片到集群
mongosh --host 10.0.1.30:27017
sh.addShard("rs0/10.0.1.10:27017,10.0.1.11:27017,10.0.1.12:27017")
sh.addShard("rs1/10.0.2.10:27017,10.0.2.11:27017,10.0.2.12:27017")
# 查看集群状态
sh.status()
分片键选择策略与数据分布
分片键(Shard Key)决定了数据如何在各分片间分布,是分片集群设计中最关键的决策。分片键选择不当会导致数据倾斜、热点写入和查询性能下降。常见分片键策略:
# 1. 启用数据库分片
sh.enableSharding("appdb")
# 2. 为集合创建分片键索引(必须在分片前创建)
# 范围分片(Range-based)适合范围查询
db.users.createIndex({ userId: 1 })
sh.shardCollection("appdb.users", { userId: 1 })
# 哈希分片(Hash-based)适合均匀分布,避免热点
db.events.createIndex({ eventId: "hashed" })
sh.shardCollection("appdb.events", { eventId: "hashed" })
# 复合分片键:兼顾范围查询和均匀分布
db.orders.createIndex({ customerId: 1, orderId: 1 })
sh.shardCollection("appdb.orders", { customerId: 1, orderId: 1 })
# 3. 查看chunk分布
sh.status()
# 关注各分片的chunk数量是否均衡
# 4. 手动触发均衡器
sh.startBalancer()
# 或关闭均衡器(维护窗口期间)
sh.stopBalancer()
分片键选择原则:高频查询字段优先,保证查询能在单个分片内完成;避免使用单调递增字段(如ObjectId、时间戳)作为范围分片键,这会导致所有写入集中在最后一个分片;哈希分片可以打散单调递增字段,但范围查询效率降低。复合分片键在第一个字段上做哈希、后续字段做范围,是一种兼顾方案。
Chunk迁移与均衡器调优
MongoDB通过Balancer组件自动迁移chunk实现分片间数据均衡。默认配置下,当分片间chunk数量差异超过阈值时触发迁移。对于写入密集场景,需要调优迁移参数避免影响正常业务:
# 查看均衡器状态
sh.getBalancerState()
# 配置均衡器窗口(在业务低峰期运行)
sh.setBalancerState(true)
db.settings.update(
{ _id: "balancer" },
{
$set: {
activeWindow: {
start: "02:00",
stop: "06:00"
}
}
},
{ upsert: true }
)
# 调整chunk迁移参数
# 最大chunk大小(默认64MB,可调为128MB减少迁移频率)
db.settings.update(
{ _id: "chunksize" },
{ $set: { value: 128 } },
{ upsert: true }
)
# 迁移并发数限制
db.settings.update(
{ _id: "balancer" },
{ $set: { maxConcurrentChunkMigrations: 2 } },
{ upsert: true }
)
# 监控迁移状态
sh.isBalancerRunning()
db.locks.find({ _id: "balancer" })
读写偏好与连接池配置
分片集群支持多种读写偏好(Read Preference)策略,根据业务场景选择从Primary或Secondary读取:
// Node.js驱动连接配置
const { MongoClient } = require('mongodb');
const client = new MongoClient(
'mongodb://10.0.1.30:27017,10.0.1.31:27017/appdb',
{
replicaSet: undefined, // 分片集群通过mongos连接
readPreference: 'secondaryPreferred', // 读写分离
readConcern: { level: 'local' }, // 读关注级别
writeConcern: { w: 'majority', j: true }, // 写关注:多数节点确认+journal持久化
maxPoolSize: 100, // 连接池最大连接数
minPoolSize: 10, // 连接池最小连接数
maxIdleTimeMS: 30000 // 空闲连接超时
}
);
// 读偏好策略说明:
// primary: 只从Primary读(默认,强一致性)
// primaryPreferred: 优先Primary,不可用时读Secondary
// secondary: 只从Secondary读
// secondaryPreferred: 优先Secondary,不可时读Primary
// nearest: 从延迟最低的节点读(不论Primary/Secondary)
// 应用示例:写入Primary,分析查询走Secondary
// 写入
await client.db('appdb')
.collection('orders')
.insertOne(order);
// 分析查询(允许读到稍旧数据)
await client.db('appdb')
.collection('orders')
.find({ status: 'completed' })
.readPreference('secondaryPreferred')
.toArray();
分片集群备份与恢复策略
分片集群的备份比单机或副本集更复杂,需要确保所有分片的时间点一致性。mongodump不适合分片集群备份,应使用文件系统快照或MongoDB Atlas备份:
# 方法1:通过mongos执行备份(一致性快照)
# 先停止均衡器防止chunk迁移
sh.stopBalancer()
# 对每个分片的Primary执行文件系统快照
# 或使用mongodump通过mongos备份
mongodump --host 10.0.1.30:27017 --db appdb --out /backup/$(date +%Y%m%d) --oplog --gzip
# 恢复均衡器
sh.startBalancer()
# 方法2:使用文件系统快照(生产推荐)
# 1. 停止均衡器
# 2. 对Config Server和所有Shard的Primary做LVM快照
# 3. 恢复均衡器
# 4. 从快照恢复时,先恢复Config Server,再恢复各Shard
# 恢复步骤
mongorestore --host 10.0.1.30:27017 --dir /backup/20260824 --gzip --drop # 可选:删除已有数据后恢复
分片集群的运维成本随分片数量线性增长。建议在单机存储接近500GB或写入QPS超过10000时再考虑分片。对于中小规模应用,副本集+SSD通常能提供足够的性能。分片后变更分片键代价极高,需要dump全量数据、重建集合、重新导入,因此在选择分片键时需要充分评估未来3-5年的数据增长模式。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mongodb-fen-pian-ji-qun-jia-gou-yu-fu-ben-ji-gao-ke-yong/