Skip to content

MongoDB 从复制集到分片集群的迁移

迁移的必要性

  1. 处理大数据量:当数据量超过单节点或单复制集的处理能力时
  2. 提高并发处理能力:分片集群可以处理更高的并发请求
  3. 增强可扩展性:支持水平扩展,方便添加新的分片
  4. 优化读写性能:将读写请求分布到多个分片上
  5. 改善数据分布:根据分片键均匀分布数据

迁移前准备

1. 评估当前环境

javascript
// 查看当前复制集状态
rs.status()

// 查看数据库统计信息
db.stats()

// 查看集合统计信息
db.collection.stats()

// 查看索引使用情况
db.collection.aggregate([{ $indexStats: {} }])

// 查看慢查询日志
db.system.profile.find({ millis: { $gt: 100 } }).sort({ millis: -1 })

2. 选择分片键

选择合适的分片键是分片集群成功的关键:

  • 高基数:分片键的取值范围要广
  • 均匀分布:数据要能均匀分布到各个分片上
  • 查询模式匹配:分片键要与常用查询模式匹配
  • 写分布:写操作要能均匀分布到各个分片上
javascript
// 分析集合的字段分布
db.collection.aggregate([
  { $group: { _id: "$field", count: { $sum: 1 } } },
  { $sort: { count: -1 } },
  { $limit: 10 }
])

// 分析集合的字段基数
db.collection.distinct("field").length

3. 规划分片集群架构

  • 分片数量:根据数据量和负载规划分片数量
  • 配置服务器:建议使用3个配置服务器(复制集模式)
  • mongos路由器:根据并发请求数量规划mongos数量
  • 硬件配置:每个分片的硬件配置应与原复制集相当或更好

4. 准备迁移工具

  • mongodump/mongorestore:用于数据迁移
  • mongos:分片集群路由器
  • 配置服务器:存储分片集群元数据
  • 分片服务器:存储实际数据
  • 监控工具:如MongoDB Atlas、Ops Manager或Prometheus+Grafana

迁移步骤

1. 部署分片集群基础架构

1.1 部署配置服务器复制集

bash
# 创建配置服务器数据目录
mkdir -p /data/configdb

# 启动配置服务器(3个节点)
mongod --configsvr --replSet configRS --port 27019 --dbpath /data/configdb --bind_ip 0.0.0.0

# 初始化配置服务器复制集
mongo --port 27019
rs.initiate({
  _id: "configRS",
  configsvr: true,
  members: [
    { _id: 0, host: "config1:27019" },
    { _id: 1, host: "config2:27019" },
    { _id: 2, host: "config3:27019" }
  ]
})

1.2 部署mongos路由器

bash
# 启动mongos路由器
mongos --configdb configRS/config1:27019,config2:27019,config3:27019 --port 27017 --bind_ip 0.0.0.0

1.3 添加初始分片

javascript
// 连接到mongos
mongo --port 27017

// 添加第一个分片(使用现有复制集)
sh.addShard("rs0/primary:27017,secondary1:27017,secondary2:27017")

2. 配置分片集群

2.1 启用数据库分片

javascript
// 连接到mongos
mongo --port 27017

// 启用数据库分片
sh.enableSharding("database_name")

2.2 选择并配置分片键

javascript
// 为集合创建索引(如果不存在)
db.collection.createIndex({ shard_key: 1 })

// 对集合进行分片
sh.shardCollection(
  "database_name.collection_name",
  { shard_key: 1 } // 分片键
)

2.3 配置分片策略

javascript
// 查看分片集群状态
sh.status()

// 查看集合分片状态
sh.status({ verbose: true })

// 调整块大小(可选,默认64MB)
db.adminCommand({ setParameter: 1, shardingChunkSizeMB: 128 })

3. 数据迁移方法

方法1:使用mongodump/mongorestore

bash
# 从复制集导出数据
mongodump --host rs0/primary:27017 --db database_name --out /backup

# 导入到分片集群
mongorestore --host mongos:27017 --db database_name /backup/database_name

方法2:使用副本集直接迁移

javascript
// 1. 将现有复制集作为第一个分片添加到分片集群
sh.addShard("rs0/primary:27017,secondary1:27017,secondary2:27017")

// 2. 启用数据库分片
sh.enableSharding("database_name")

// 3. 对集合进行分片
sh.shardCollection(
  "database_name.collection_name",
  { shard_key: 1 }
)

// 4. 启动均衡器
sh.startBalancer()

// 5. 监控均衡过程
sh.getBalancerState()
sh.getBalancerWindow()

方法3:使用change streams进行增量迁移

javascript
// 1. 在源复制集上创建change stream
const sourceCollection = sourceDB.collection("collection_name")
const changeStream = sourceCollection.watch()

// 2. 在目标分片集群上插入初始数据
mongodump --host source:27017 --db database_name --collection collection_name --out /backup
mongorestore --host target:27017 --db database_name --collection collection_name /backup/database_name/collection_name.bson

// 3. 应用change stream中的增量更改
changeStream.on("change", (change) => {
  // 应用更改到目标集群
  applyChangeToTarget(change)
})

4. 添加更多分片

javascript
// 部署新的复制集作为分片
// ...

// 添加新分片到集群
sh.addShard("rs1/primary:27017,secondary1:27017,secondary2:27017")

// 监控均衡过程
sh.status()

// 查看块分布
sh.status({ verbose: true })

5. 配置应用程序连接

javascript
// 修改应用程序连接字符串
// 从:
// mongodb://primary:27017,secondary1:27017,secondary2:27017/database_name?replicaSet=rs0

// 改为:
mongodb://mongos1:27017,mongos2:27017,mongos3:27017/database_name?readPreference=secondaryPreferred

迁移后验证

1. 验证数据完整性

javascript
// 比较源复制集和目标分片集群的数据量
sourceCount = sourceDB.collection("collection_name").countDocuments()
targetCount = targetDB.collection("collection_name").countDocuments()
print(`源数据量: ${sourceCount}, 目标数据量: ${targetCount}, 一致: ${sourceCount === targetCount}`)

// 随机验证文档
const randomDocs = sourceDB.collection("collection_name").aggregate([{ $sample: { size: 10 } }]).toArray()
randomDocs.forEach(doc => {
  const targetDoc = targetDB.collection("collection_name").findOne({ _id: doc._id })
  print(`文档 ${doc._id} 存在: ${targetDoc !== null}`)
})

2. 验证查询性能

javascript
// 使用explain()分析查询计划
db.collection.find({ shard_key: "value" }).explain("executionStats")

// 查看查询是否使用了索引
// 检查executionStats.executionStages.stage是否为IXSCAN

3. 验证分片分布

javascript
// 查看分片分布
sh.status({ verbose: true })

// 查看集合的块分布
db.collection.getShardDistribution()

// 查看每个分片的数据量
db.collection.aggregate([
  { $shard: { } },
  { $group: { _id: "$shard", count: { $sum: 1 } } }
])

4. 验证均衡器状态

javascript
// 查看均衡器状态
sh.getBalancerState()

// 查看均衡器窗口
sh.getBalancerWindow()

// 查看当前正在进行的均衡操作
db.adminCommand({ currentOp: 1, $all: true }).inprog.filter(op => op.msg && op.msg.includes("migrate"))

迁移最佳实践

1. 选择合适的迁移时间

  • 选择业务低峰期进行迁移
  • 预留足够的迁移时间
  • 准备回滚方案

2. 优化分片键

  • 选择高基数、均匀分布的字段作为分片键
  • 考虑查询模式,确保常用查询能使用分片键
  • 避免选择频繁更新的字段作为分片键

3. 监控迁移过程

  • 监控数据迁移进度
  • 监控分片集群性能
  • 监控均衡器状态
  • 监控资源使用情况

4. 逐步迁移

  • 先迁移非核心业务数据
  • 验证成功后再迁移核心业务数据
  • 逐步切换应用程序流量

5. 测试故障恢复

  • 测试mongos故障恢复
  • 测试配置服务器故障恢复
  • 测试分片故障恢复
  • 测试均衡器故障恢复

6. 优化应用程序

  • 更新应用程序连接字符串
  • 优化查询以利用分片键
  • 考虑使用读写分离
  • 调整连接池配置

常见问题及解决方案

1. 分片键选择不当

问题:数据分布不均匀,某些分片过载

解决方案

  • 重新评估分片键选择
  • 考虑使用复合分片键
  • 考虑使用哈希分片
  • 如需更改分片键,可能需要重新迁移数据

2. 迁移过程中数据不一致

问题:源复制集和目标分片集群数据不一致

解决方案

  • 使用事务确保数据一致性
  • 使用change streams进行增量同步
  • 迁移完成后进行数据验证
  • 考虑使用专业的数据迁移工具

3. 均衡器性能问题

问题:均衡器迁移数据导致系统负载过高

解决方案

  • 调整均衡器窗口,在低峰期运行
  • 调整块大小
  • 暂时禁用均衡器,手动迁移块
  • 优化硬件配置

4. 查询性能下降

问题:迁移后查询性能下降

解决方案

  • 优化查询以利用分片键
  • 确保查询能路由到单个分片
  • 创建合适的索引
  • 调整mongos数量

5. 应用程序连接问题

问题:应用程序无法连接到分片集群

解决方案

  • 检查mongos地址和端口
  • 检查防火墙设置
  • 检查认证配置
  • 检查连接字符串格式

监控与维护

1. 监控指标

  • mongos指标:连接数、查询延迟、吞吐量
  • 配置服务器指标:CPU、内存、磁盘I/O
  • 分片指标:CPU、内存、磁盘I/O、网络流量
  • 均衡器指标:均衡操作数量、迁移时间、块分布
  • 数据分布:每个分片的数据量、块数量

2. 日常维护

  • 定期检查分片分布
  • 监控慢查询日志
  • 定期备份配置数据库
  • 检查索引使用情况
  • 监控资源使用情况

3. 扩展分片集群

javascript
// 添加新分片
sh.addShard("rs2/primary:27017,secondary1:27017,secondary2:27017")

// 监控均衡过程
sh.status()

// 调整块大小
sh.setBalancerState(true)

迁移案例

案例1:电商平台迁移

背景:某电商平台数据量达到5TB,单复制集处理能力不足

解决方案

  1. 选择user_id作为分片键(高基数、均匀分布)
  2. 部署3个分片,每个分片使用3节点复制集
  3. 使用现有复制集作为第一个分片
  4. 对集合进行分片,启用均衡器
  5. 逐步迁移应用程序流量

结果

  • 读写性能提升3倍
  • 支持水平扩展
  • 能够处理更高的并发请求

案例2:日志系统迁移

背景:某日志系统每天生成1TB日志,查询性能下降

解决方案

  1. 选择timestamp作为分片键
  2. 使用时间范围分片,每个分片存储一个月的日志
  3. 部署5个分片,支持5个月的热数据
  4. 使用mongodump/mongorestore迁移历史数据
  5. 使用change streams同步实时数据

结果

  • 查询性能提升10倍
  • 支持快速查询任意时间范围的日志
  • 方便添加新的分片存储更多日志

常见问题(FAQ)

Q1: 迁移过程中是否需要停机?

A1: 这取决于迁移方法:

  • 使用mongodump/mongorestore:需要停机或只读
  • 使用副本集直接迁移:不需要停机
  • 使用change streams:不需要停机,支持增量迁移

Q2: 如何选择合适的分片键?

A2: 选择分片键的原则:

  • 高基数:取值范围广
  • 均匀分布:数据能均匀分布到各个分片
  • 查询模式匹配:与常用查询模式匹配
  • 写分布:写操作能均匀分布

Q3: 分片集群的最小配置是什么?

A3: 分片集群的最小配置:

  • 1个mongos路由器
  • 3个配置服务器(复制集模式)
  • 1个分片(复制集模式,至少3个节点)

Q4: 如何监控分片集群?

A4: 监控分片集群的方法:

  • 使用MongoDB Atlas或Ops Manager进行可视化监控
  • 使用Prometheus + Grafana监控指标
  • 使用mongostat和mongotop实时监控
  • 定期查看慢查询日志

Q5: 如何处理分片故障?

A5: 处理分片故障的步骤:

  1. 监控系统检测到分片故障
  2. 自动将流量路由到其他分片
  3. 修复或替换故障分片
  4. 将修复后的分片重新添加到集群
  5. 启动均衡器重新分布数据

Q6: 均衡器如何工作?

A6: 均衡器的工作原理:

  • 监控每个分片的块数量
  • 当分片间块数量差异超过阈值时,启动迁移
  • 将块从块数量多的分片迁移到块数量少的分片
  • 迁移过程使用oplog进行同步

Q7: 如何调整均衡器运行时间?

A7: 调整均衡器窗口:

javascript
// 设置均衡器窗口
sh.setBalancerWindow({
  start: "22:00",
  stop: "06:00"
})

// 查看均衡器窗口
sh.getBalancerWindow()

Q8: 如何禁用均衡器?

A8: 禁用均衡器:

javascript
// 禁用均衡器
sh.stopBalancer()

// 查看均衡器状态
sh.getBalancerState()

Q9: 迁移后如何优化查询?

A9: 迁移后查询优化方法:

  • 确保查询包含分片键
  • 避免跨分片查询
  • 创建合适的索引
  • 考虑使用覆盖索引
  • 优化聚合管道

Q10: 如何备份分片集群?

A10: 分片集群备份方法:

  • 使用mongodump备份每个分片
  • 使用mongodump备份配置数据库
  • 使用MongoDB Atlas或Ops Manager进行自动备份
  • 考虑使用文件系统快照备份

Q11: 分片集群的安全性如何保障?

A11: 分片集群的安全措施:

  • 启用认证和授权
  • 使用TLS/SSL加密传输
  • 配置IP白名单
  • 定期更新密码
  • 审计日志

Q12: 如何扩展分片集群?

A12: 扩展分片集群的步骤:

  1. 部署新的复制集
  2. 将新复制集添加为分片
  3. 启动均衡器
  4. 监控数据分布
  5. 调整应用程序配置

迁移后的优化

  1. 优化查询:确保查询能利用分片键,避免跨分片查询
  2. 调整索引:根据查询模式创建合适的索引
  3. 优化应用程序:调整连接池配置,使用读写分离
  4. 监控性能:定期监控分片集群性能,及时调整配置
  5. 规划容量:根据数据增长趋势,提前规划分片数量
  6. 定期维护:定期检查分片分布,清理不使用的索引

通过以上步骤和最佳实践,可以顺利完成从MongoDB复制集到分片集群的迁移,提高系统的可扩展性和性能。