MongoDB分片集群架构
MongoDB分片集群通过水平分片将数据分散到多个节点,解决单机存储容量和吞吐量瓶颈。集群由三个组件构成:Shard(分片节点,每个分片是一个副本集)、Config Server(配置服务器,存储集群元数据和路由信息)、Mongos(路由进程,接收客户端请求并路由到对应分片)。
分片的核心机制是分片键(Shard Key)。MongoDB根据分片键的值将数据划分为多个chunk,每个chunk存储一段连续的分片键范围,chunk在分片之间自动迁移以实现数据均衡。分片键的选择直接决定了集群的扩展能力和查询性能。
分片键选择策略
分片键是分片集群设计中最重要的决策,选择不当会导致数据热点、查询性能下降和集群不可扩展。分片键一旦设置无法修改(MongoDB 4.4之前),选择需要谨慎评估。
理想的分片键应具备三个特性:高基数(值分布广泛,避免数据倾斜)、低频率(单个值对应的文档数量少)、非单调递增(避免所有写入集中到最后一个分片)。
// 方案1:哈希分片(适合单调递增的_id字段)
sh.shardCollection("ecommerce.orders", { "_id": "hashed" })
// 优点:写入均匀分散
// 缺点:范围查询需要scatter-gather到所有分片
// 方案2:范围分片(适合范围查询频繁的字段)
sh.shardCollection("ecommerce.orders", { "user_id": 1, "created_at": 1 })
// 优点:按user_id+时间的范围查询只命中单个分片
// 缺点:user_id分布不均匀时可能热点
// 方案3:复合分片键(兼顾查询和均匀分布)
sh.shardCollection("ecommerce.orders", {
"region": 1, // 区域:低基数,支持区域查询
"user_id": "hashed" // 用户ID哈希:高基数,均匀分布
})
范围分片适合范围查询场景,但单调递增的分片键会导致写入热点。哈希分片将分片键值通过哈希函数映射到chunk,写入均匀分散但范围查询效率低。复合分片键结合两者优势:第一键段使用低基数字段支持区域查询,第二键段使用哈希保证均匀分布。
常见的选择误区:使用 ObjectId 作为范围分片键,由于ObjectId单调递增,所有新写入都路由到最后一个分片,造成热点。正确做法是使用 { _id: “hashed” } 或添加随机前缀。
副本集配置与高可用
每个分片是一个副本集,副本集提供数据冗余和自动故障转移。标准配置为3节点:1个Primary、2个Secondary,或扩展为5节点以获得更高的可用性。
// 初始化副本集(每个分片执行)
rs.initiate({
_id: "shard1",
members: [
{ _id: 0, host: "shard1-node1:27018", priority: 2 },
{ _id: 1, host: "shard1-node2:27018", priority: 1 },
{ _id: 2, host: "shard1-node3:27018",
priority: 0, hidden: true, // 隐藏节点,不接收读请求
votes: 1 } // 参与选举投票
],
settings: {
heartbeatIntervalMillis: 2000,
electionTimeoutMillis: 10000
}
})
// 查看副本集状态
rs.status()
rs.conf()
// 添加仲裁节点(不存储数据,仅参与选举)
rs.addArb("shard1-arb:27018")
priority参数控制选举优先级,priority最高的节点在选举中优先成为Primary。hidden节点对客户端不可见,常用于备份、分析或专门的工作负载。electionTimeoutMillis设置选举超时时间,Primary心跳超时后触发选举,默认10秒。调整心跳间隔和超时可以加快故障检测速度,但过短的间隔可能导致网络抖动引起的误判。
完整分片集群搭建
# 1. 启动Config Server副本集(3节点)
mongod --configsvr --replSet cfgReplSet \
--bind_ip_all --port 27019 \
--dbpath /data/config --logpath /var/log/mongo/config.log --fork
# 初始化Config Server
mongo --port 27019 --eval '
rs.initiate({
_id: "cfgReplSet",
configsvr: true,
members: [
{ _id: 0, host: "cfg1:27019" },
{ _id: 1, host: "cfg2:27019" },
{ _id: 2, host: "cfg3:27019" }
]
})'
# 2. 启动分片副本集(以shard1为例,3节点)
mongod --shardsvr --replSet shard1ReplSet \
--bind_ip_all --port 27018 \
--dbpath /data/shard1 --logpath /var/log/mongo/shard1.log --fork
# 初始化shard1副本集
mongo --port 27018 --eval '
rs.initiate({
_id: "shard1ReplSet",
members: [
{ _id: 0, host: "shard1-node1:27018" },
{ _id: 1, host: "shard1-node2:27018" },
{ _id: 2, host: "shard1-node3:27018" }
]
})'
# 3. 启动Mongos路由
mongos --configdb cfgReplSet/cfg1:27019,cfg2:27019,cfg3:27019 \
--bind_ip_all --port 27017 --logpath /var/log/mongo/mongos.log --fork
# 4. 添加分片到集群
mongo --port 27017 --eval '
sh.addShard("shard1ReplSet/shard1-node1:27018,shard1-node2:27018")
sh.addShard("shard2ReplSet/shard2-node1:27018,shard2-node2:27018")
sh.addShard("shard3ReplSet/shard3-node1:27018,shard3-node2:27018")'
# 5. 启用分片
mongo --port 27017 --eval '
sh.enableSharding("ecommerce")
sh.shardCollection("ecommerce.orders", { "region": 1, "user_id": "hashed" })'
# 查看集群状态
sh.status()
数据均衡与chunk迁移
MongoDB的Balancer进程自动在分片之间迁移chunk,使各分片数据量趋于均衡。Balancer默认运行在Config Server的Primary上,检测到分片间chunk数量差异超过阈值时触发迁移。
// 查看Balancer状态
sh.getBalancerState()
sh.isBalancerRunning()
// 设置均衡窗口(只在业务低峰期运行)
db.settings.update(
{ _id: "balancer" },
{ $set: { activeWindow: { start: "02:00", stop: "06:00" } } },
{ upsert: true }
)
// 手动迁移chunk
sh.moveChunk("ecommerce.orders",
{ region: "east", user_id: MinKey }, "shard2")
// 设置集合的chunk大小(默认64MB)
db.runCommand({
collMod: "ecommerce.orders",
chunkSize: 128 // MB
})
chunk迁移过程涉及数据复制和元数据更新,大量迁移会消耗网络带宽和IO资源。生产环境中建议设置均衡窗口在低峰期运行,避免影响正常业务。chunk大小影响迁移频率和查询效率:chunk过小导致元数据膨胀和频繁迁移,过大则迁移耗时增加。
读偏好与写关注配置
分片集群中客户端通过Mongos连接,读写路由策略通过readPreference和writeConcern控制。
// 连接URI配置读偏好
mongodb://mongos1:27017,mongos2:27017/ecommerce?
replicaSet=cfgReplSet
&readPreference=primaryPreferred
&w=majority
&wtimeoutMS=5000
// 读偏好选项
// primary: 只从Primary读(强一致性)
// primaryPreferred: 优先Primary,故障时读Secondary
// secondary: 只从Secondary读(减轻Primary压力)
// secondaryPreferred: 优先Secondary
// nearest: 最低延迟节点
// 查询级别指定读偏好
db.orders.find({ region: "east" })
.readPref("secondaryPreferred", [{ region: "east" }])
// 写关注配置
db.orders.insertOne(
{ user_id: "u123", amount: 99.9 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)
// w: "majority" - 确认写入到多数节点
// j: true - 确认写入到journal日志
// wtimeout: 写入超时时间
w: “majority”配合j: true确保数据写入到多数节点的持久化日志后才返回成功,这是保证数据不丢失的最低要求。读偏好secondaryPreferred将读请求分散到Secondary节点,减轻Primary压力,但需要接受最终一致性。对于对一致性要求高的读操作,应使用primary或primaryPreferred。
分片集群运维与监控
分片集群的运维需要关注chunk分布、迁移状态、各分片负载和连接数。
// 查看各分片的数据分布
db.getSiblingDB("config").chunks.aggregate([
{ $group: {
_id: "$shard",
chunkCount: { $sum: 1 },
minKey: { $min: "$min" },
maxKey: { $max: "$max" }
}}
])
// 查看集合的chunk分布
sh.getBalancerState()
db.getSiblingDB("config").chunks.find({
ns: "ecommerce.orders"
}).count()
// 监控指标(MongoDB Atlas或Prometheus exporter)
// - 各分片的opcounters(增删改查计数)
// - chunk迁移队列长度
// - Balancer运行状态
// - 连接数和连接池利用率
// - 各分片磁盘使用率
分片集群扩展时添加新分片,Balancer会自动将部分chunk迁移到新分片。迁移过程中可能出现短暂的性能下降,建议在低峰期添加分片并监控迁移进度。对于超大集合(TB级别),可以预先在目标分片上创建空chunk减少迁移量。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mongodb-fen-pian-ji-qun-da-jian-shi-zhan-fen-pian-jian-xuan/