MongoDB分片集群搭建实战:Shard路由与副本集高可用架构

MongoDB分片集群(Sharded Cluster)通过水平拆分数据到多个Shard节点实现横向扩展,解决单机存储容量和写入吞吐量瓶颈。当数据量超过单机内存和磁盘限制,或写入QPS超过单节点处理能力时,分片集群是主要扩展方案。分片集群由三个核心组件构成:Shard(数据分片)、Config Server(配置服务器)和Mongos(路由代理),配合副本集实现高可用。

分片集群架构规划

生产环境分片集群至少需要9个MongoDB实例:3个Config Server(副本集)、2个Shard(各为3节点副本集)、1个Mongos路由。以两分片为例的拓扑结构:

# 集群拓扑规划
# Config Server Replica Set (CSRS): 3节点
cfg1: 10.0.0.11:27019
cfg2: 10.0.0.12:27019
cfg3: 10.0.0.13:27019

# Shard1 Replica Set: 3节点(1 Primary + 2 Secondary)
shard1-n1: 10.0.0.21:27018
shard1-n2: 10.0.0.22:27018
shard1-n3: 10.0.0.23:27018

# Shard2 Replica Set: 3节点
shard2-n1: 10.0.0.31:27018
shard2-n2: 10.0.0.32:27018
shard2-n3: 10.0.0.33:27018

# Mongos Router
mongos: 10.0.0.41:27017

# 每台机器的mongod.conf配置
storage:
  dbPath: /data/mongodb
  journal:
    enabled: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 8  # WiredTiger缓存大小
systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logAppend: true
net:
  port: 27018
  bindIp: 0.0.0.0
replication:
  replSetName: shard1  # 副本集名称
sharding:
  clusterRole: shardsvr  # 分片角色

Config Server副本集初始化

Config Server存储集群元数据(chunk分布、分片信息、数据库路由表),必须部署为副本集保证高可用。MongoDB 4.2+要求Config Server使用CSRS(Config Server Replica Set)模式:

# 在3台Config Server上分别启动mongod
mongod --config /etc/mongod-cfg.conf

# 连接到第一台Config Server,初始化副本集
mongosh --host 10.0.0.11:27019

rs.initiate({
  _id: "cfgReplSet",
  configsvr: true,  // 声明为Config Server副本集
  members: [
    { _id: 0, host: "10.0.0.11:27019" },
    { _id: 1, host: "10.0.0.12:27019" },
    { _id: 2, host: "10.0.0.13:27019" }
  ]
})

// 验证副本集状态
rs.status()
// 确认一个节点为PRIMARY,两个节点为SECONDARY

// Config Server的oplog大小建议设为1GB以上
cfg = rs.conf()
cfg.members[0].priority = 2  // 优先选举第一节点为Primary
rs.reconfig(cfg)

Shard副本集与Mongos路由配置

每个Shard是独立的副本集,各自管理一部分数据。Mongos是无状态的路由进程,接收客户端请求后根据Config Server的元数据将操作路由到正确的Shard:

# 初始化Shard1副本集
mongosh --host 10.0.0.21:27018
rs.initiate({
  _id: "shard1ReplSet",
  members: [
    { _id: 0, host: "10.0.0.21:27018", priority: 2 },
    { _id: 1, host: "10.0.0.22:27018" },
    { _id: 2, host: "10.0.0.23:27018" }
  ]
})

# 初始化Shard2副本集
mongosh --host 10.0.0.31:27018
rs.initiate({
  _id: "shard2ReplSet",
  members: [
    { _id: 0, host: "10.0.0.31:27018", priority: 2 },
    { _id: 1, host: "10.0.0.32:27018" },
    { _id: 2, host: "10.0.0.33:27018" }
  ]
})

# 启动Mongos,指向Config Server副本集
mongos --configdb cfgReplSet/10.0.0.11:27019,10.0.0.12:27019,10.0.0.13:27019 \
       --port 27017 --bind_ip 0.0.0.0 \
       --logpath /var/log/mongodb/mongos.log

# 连接到Mongos,添加分片到集群
mongosh --host 10.0.0.41:27017

// 添加Shard1
sh.addShard("shard1ReplSet/10.0.0.21:27018,10.0.0.22:27018,10.0.0.23:27018")

// 添加Shard2
sh.addShard("shard2ReplSet/10.0.0.31:27018,10.0.0.32:27018,10.0.0.33:27018")

// 查看集群状态
sh.status()
// 输出应显示2个分片,每个分片为3节点副本集

// 启用分片(对数据库级别)
sh.enableSharding("ecommerce")

// 对集合设置分片(需先指定分片键)
sh.shardCollection("ecommerce.orders", { "user_id": 1, "created_at": 1 })

分片键选择策略

分片键(Shard Key)决定数据在Shard间的分布方式,直接影响查询效率和负载均衡。选择分片键需考虑三个因素:基数(Cardinality)、分布均匀性、查询定向能力。常见的分片键策略:

// 策略一:范围分片(Range-based Sharding)
// 适合范围查询,但容易导致热点写入
sh.shardCollection("ecommerce.orders", { "order_id": 1 })
// order_id递增 -> 新数据总是写入最后一个Shard -> 写入热点

// 策略二:哈希分片(Hash-based Sharding)
// 数据均匀分布,但范围查询需扫描所有Shard
sh.shardCollection("ecommerce.orders", { "user_id": "hashed" })
// user_id哈希值 -> 数据均匀分布到各Shard -> 写入负载均衡

// 策略三:复合分片键(Compound Shard Key)
// 兼顾范围查询和分布均匀性
sh.shardCollection("ecommerce.orders", { "user_id": 1, "created_at": 1 })
// 先按user_id分片 -> 同一用户数据在同一Shard
// 再按created_at排序 -> 支持时间范围查询

// 策略四:区域分片(Zone Sharding)
// 将特定数据固定到特定Shard(如按地理位置)
sh.addShardTag("shard1ReplSet", "US")
sh.addShardTag("shard2ReplSet", "EU")

sh.addTagRange("ecommerce.orders", 
  { "region": "US" }, { "region": "EU" }, "US")
sh.addTagRange("ecommerce.orders",
  { "region": "EU" }, { "region": "US" }, "EU")

// 查看chunk分布
sh.status()
// 每个chunk默认64MB,超过后触发分裂
// 均衡器(Balancer)自动迁移chunk实现负载均衡

分片键在集合分片后不可更改(MongoDB 4.4+支持refineCollectionShardKey添加前缀字段)。分片键必须包含在所有唯一索引的前缀中,否则无法创建唯一索引。推荐使用高基数字段(如user_id)配合hashed分片,或user_id+timestamp复合键实现读写均衡。

数据迁移与Balancer管理

MongoDB Balancer在后台自动迁移chunk,当Shard间chunk数量差异超过阈值(默认8个)时触发迁移。迁移过程中数据先复制到目标Shard,确认完整后更新路由表,最后删除源Shard数据。生产环境可在低峰期手动控制Balancer:

# 查看Balancer状态
sh.getBalancerState()

# 临时停止Balancer(维护窗口)
sh.stopBalancer()
# 或设置Balancer窗口(仅在指定时间段运行)
sh.setBalancerState(true)
sh.updateZoneKeyRange(
  "ecommerce.orders",
  { "user_id": MinKey },
  { "user_id": MaxKey },
  null  // 移除zone约束
)

// 设置Balancer运行窗口(凌晨2-6点)
db.settings.update(
  { _id: "balancer" },
  { $set: { "activeWindow": { "start": "02:00", "stop": "06:00" } } },
  { upsert: true }
)

// 手动迁移chunk
sh.moveChunk("ecommerce.orders", { user_id: 10000 }, "shard2ReplSet")

// 查看迁移进度
sh.status()
db.currentOp({ "ns": "ecommerce.orders", "op": "command" })

# 重新启动Balancer
sh.startBalancer()

查询路由与性能优化

Mongos将查询分为两种类型:定向查询(Targeted Query)和广播查询(Scatter-Gather Query)。包含分片键的查询可以路由到特定Shard,否则需查询所有Shard再合并结果。广播查询的延迟与Shard数量正相关:

// 定向查询(高性能):查询条件包含分片键
db.orders.find({ user_id: 12345 })  // Mongos直接路由到user_id所在的Shard

// 广播查询(低性能):查询条件不含分片键
db.orders.find({ status: "shipped" })  // Mongos向所有Shard发送查询
// 优化:确保高频查询包含分片键

// 使用explain分析查询路由
db.orders.find({ user_id: 12345 }).explain("executionStats")
// 查看 "shards" 字段确认查询命中了哪些Shard
// "winningPlan" 中 "SHARD_MERGE" 表示广播查询

// 索引优化:确保查询字段有索引
db.orders.createIndex({ user_id: 1, created_at: -1 })
db.orders.createIndex({ user_id: 1, status: 1 })

// 使用$hint强制指定索引
db.orders.find({ user_id: 12345, status: "shipped" })
  .hint({ user_id: 1, status: 1 })

监控与运维

MongoDB分片集群的监控关注chunk分布均衡性、迁移频率、Mongos连接数和各Shard的负载差异。通过MongoDB Atlas或Prometheus + mongodb_exporter采集指标:

# 关键监控指标
# 1. Chunk分布
sh.status()  // 查看各Shard的chunk数量
db.getSiblingDB("config").chunks.aggregate([
  { $group: { _id: "$shard", count: { $sum: 1 } } }
])

# 2. 迁移统计
db.getSiblingDB("config").getCollection("migrations").stats()

# 3. 各Shard存储使用
db.runCommand({ serverStatus: 1, sharding: 1 })

# 4. Mongos连接数与延迟
db.runCommand({ serverStatus: 1, connections: 1 })
db.adminCommand({ "connPoolStats": 1 })

# 常见运维操作
# 添加新Shard扩容
sh.addShard("shard3ReplSet/10.0.0.41:27018,10.0.0.42:27018,10.0.0.43:27018")
// 添加后Balancer自动迁移部分chunk到新Shard

# 移除Shard(需先迁移数据)
db.adminCommand({ removeShard: "shard2ReplSet" })
// 返回迁移进度,完成后再次执行确认移除

# 备份分片集群(使用mongodump通过Mongos)
mongodump --host 10.0.0.41:27017 --db ecommerce --out /backup/$(date +%Y%m%d)
// 或使用文件系统快照(每个Shard独立备份)

分片集群运维的核心原则是保证chunk分布均匀、分片键查询覆盖率高、Balancer运行正常。当单集合数据量超过1TB或写入QPS超过3万时考虑分片;数据量500GB以内时单节点副本集+SSD通常能满足需求,过早分片会增加运维复杂度而不带来显著性能提升。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mongodb-fen-pian-ji-qun-da-jian-shi-zhan-shard-lu-you-yu-fu/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐