MongoDB分片集群架构与副本集高可用配置实战

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/

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

相关推荐