本文主要介绍MongoDB数据库分表分库的原理与实践,包括分片策略、分片键选择、数据迁移等内容。风哥教程参考MongoDB官方文档Sharding相关章节。
目录大纲
Part01-基础概念与理论知识
1.1 分表分库概述
分表分库是解决大数据量和高并发场景下数据库性能瓶颈的有效手段。通过将数据分散到多个节点,可以实现水平扩展,提高系统的存储容量和处理能力。
分表分库的主要目的:
- 解决单库性能瓶颈:分散读写压力,提高并发处理能力
- 解决单库存储限制:突破单机存储容量限制
- 提高可用性:单个节点故障不影响整体服务
- 便于维护:可以独立维护各个分片
分表分库的基本概念:
- 分片(Sharding):将数据水平分割到多个节点
- 分片键(Shard Key):用于确定数据分布的字段
- Chunk:数据的最小迁移单位,默认64MB
- Balancer:负责在分片间迁移数据的进程
学习交流加群风哥微信: itpux-com
1.2 分片策略
MongoDB支持的分片策略:
- 范围分片(Range Sharding):根据分片键的范围进行分片
- 哈希分片(Hashed Sharding):根据分片键的哈希值进行分片
- 标签分片(Zone Sharding):根据数据的地理位置或业务属性进行分片
范围分片的特点:
- 适合范围查询
- 数据分布可能不均匀
- 容易出现热点问题
哈希分片的特点:
- 数据分布均匀
- 不适合范围查询
- 避免热点问题
更多视频教程www.fgedu.net.cn
Part02-生产环境规划与建议
2.1 分片架构设计
分片集群架构组件:
- Config Server:存储集群元数据,建议部署3节点副本集
- Mongos:路由进程,处理客户端请求,建议部署多个实例
- Shard:数据分片,每个分片是一个副本集
架构设计建议:
- Config Server使用独立的高性能服务器
- 部署多个Mongos实例,使用负载均衡
- 每个Shard配置3节点副本集
- 考虑跨数据中心部署,提高可用性
风哥提示:分片架构设计需要考虑数据量、查询模式和扩展性。
2.2 分片键选择
分片键选择原则:
- 高基数:分片键应该有足够多的不同值
- 均匀分布:避免数据倾斜和热点
- 查询友好:常用查询条件应包含分片键
- 不可变:分片键值不应频繁修改
分片键选择建议:
- 避免使用单调递增的字段(如自增ID)
- 考虑使用复合分片键
- 对于时间序列数据,考虑使用时间字段+其他字段
- 对于用户数据,考虑使用用户ID或地理位置
更多学习教程公众号风哥教程itpux_com
Part03-生产环境项目实施方案
3.1 分片集群部署
分片集群部署步骤:
# 1. 部署Config
Server副本集
# 在3台服务器上启动Config Server
mongod –configsvr –replSet fgedu-config –dbpath /mongodb/config –port 27019 –bind_ip 0.0.0.0 –fork –logpath /mongodb/logs/config.log
# 初始化Config Server
mongosh –host 192.168.1.100 –port 27019
rs.initiate({
_id: “fgedu-config”,
configsvr: true,
members: [
{ _id: 0, host: “192.168.1.100:27019” },
{ _id: 1, host: “192.168.1.101:27019” },
{ _id: 2, host: “192.168.1.102:27019” }
]
})
# 2. 部署Shard副本集
# Shard1
mongod –shardsvr –replSet fgedu-shard1 –dbpath /mongodb/shard1 –port 27018 –bind_ip 0.0.0.0 –fork –logpath /mongodb/logs/shard1.log
mongosh –host 192.168.1.100 –port 27018
rs.initiate({
_id: “fgedu-shard1”,
members: [
{ _id: 0, host: “192.168.1.100:27018” },
{ _id: 1, host: “192.168.1.101:27018” },
{ _id: 2, host: “192.168.1.102:27018” }
]
})
# Shard2
mongod –shardsvr –replSet fgedu-shard2 –dbpath /mongodb/shard2 –port 27028 –bind_ip 0.0.0.0 –fork –logpath /mongodb/logs/shard2.log
mongosh –host 192.168.1.100 –port 27028
rs.initiate({
_id: “fgedu-shard2”,
members: [
{ _id: 0, host: “192.168.1.100:27028” },
{ _id: 1, host: “192.168.1.101:27028” },
{ _id: 2, host: “192.168.1.102:27028” }
]
})
# 3. 启动Mongos路由
mongos –configdb fgedu-config/192.168.1.100:27019,192.168.1.101:27019,192.168.1.102:27019 –port 27017 –bind_ip 0.0.0.0 –fork –logpath /mongodb/logs/mongos.log
3.2 分片配置
分片配置:
# 1. 连接到Mongos
mongosh –host 192.168.1.100 –port 27017
# 2. 添加分片
sh.addShard(“fgedu-shard1/192.168.1.100:27018,192.168.1.101:27018,192.168.1.102:27018”)
sh.addShard(“fgedu-shard2/192.168.1.100:27028,192.168.1.101:27028,192.168.1.102:27028”)
# 3. 查看分片状态
sh.status()
# 4. 启用数据库分片
sh.enableSharding(“fgedudb”)
# 5. 对集合进行分片
# 哈希分片
sh.shardCollection(“fgedudb.fgedu_users”, { user_id: “hashed” })
# 范围分片
sh.shardCollection(“fgedudb.fgedu_orders”, { order_date: 1 })
# 复合分片键
sh.shardCollection(“fgedudb.fgedu_logs”, { region: 1, timestamp: 1 })
# 6. 查看分片分布
db.fgedu_users.getShardDistribution()
3.3 数据迁移
数据迁移:
# 1. 手动迁移Chunk
sh.moveChunk(“fgedudb.fgedu_users”, { user_id: MinKey }, “fgedu-shard2”)
# 2. 查看Chunk分布
db.chunks.find({ ns: “fgedudb.fgedu_users” }).pretty()
# 3. 配置Balancer
# 开启Balancer
sh.startBalancer()
# 关闭Balancer
sh.stopBalancer()
# 查看Balancer状态
sh.getBalancerState()
# 4. 设置迁移窗口
use config
db.settings.update(
{ _id: “balancer” },
{ $set: { activeWindow: { start: “02:00”, stop: “06:00” } } },
{ upsert: true }
)
# 5. 查看迁移状态
sh.isBalancerRunning()
Part04-生产案例与实战讲解
4.1 分片集群部署实战
分片集群部署实战:
# 场景:部署2个分片的分片集群
# 1. 部署Config
Server
# 在3台服务器上执行
mkdir -p /mongodb/config /mongodb/logs
mongod –configsvr \
–replSet fgedu-config \
–dbpath /mongodb/config \
–port 27019 \
–bind_ip 0.0.0.0 \
–fork \
–logpath /mongodb/logs/config.log \
–auth
# 初始化Config Server
mongosh –host 192.168.1.100 –port 27019
> rs.initiate({
… _id: “fgedu-config”,
… configsvr: true,
… members: [
… { _id: 0, host: “192.168.1.100:27019” },
… { _id: 1, host: “192.168.1.101:27019” },
… { _id: 2, host: “192.168.1.102:27019” }
… ]
… })
# 输出:
{
“ok”: 1,
“operationTime”: Timestamp(1617820800, 1)
}
# 2. 部署Shard1
mkdir -p /mongodb/shard1 /mongodb/logs
mongod –shardsvr \
–replSet fgedu-shard1 \
–dbpath /mongodb/shard1 \
–port 27018 \
–bind_ip 0.0.0.0 \
–fork \
–logpath /mongodb/logs/shard1.log \
–auth
mongosh –host 192.168.1.100 –port 27018
> rs.initiate({
… _id: “fgedu-shard1”,
… members: [
… { _id: 0, host: “192.168.1.100:27018” },
… { _id: 1, host: “192.168.1.101:27018” },
… { _id: 2, host: “192.168.1.102:27018” }
… ]
… })
# 3. 部署Shard2
mkdir -p /mongodb/shard2 /mongodb/logs
mongod –shardsvr \
–replSet fgedu-shard2 \
–dbpath /mongodb/shard2 \
–port 27028 \
–bind_ip 0.0.0.0 \
–fork \
–logpath /mongodb/logs/shard2.log \
–auth
mongosh –host 192.168.1.100 –port 27028
> rs.initiate({
… _id: “fgedu-shard2”,
… members: [
… { _id: 0, host: “192.168.1.100:27028” },
… { _id: 1, host: “192.168.1.101:27028” },
… { _id: 2, host: “192.168.1.102:27028” }
… ]
… })
# 4. 启动Mongos
mongos –configdb fgedu-config/192.168.1.100:27019,192.168.1.101:27019,192.168.1.102:27019 \
–port 27017 \
–bind_ip 0.0.0.0 \
–fork \
–logpath /mongodb/logs/mongos.log
# 5. 配置分片
mongosh –host 192.168.1.100 –port 27017
> sh.addShard(“fgedu-shard1/192.168.1.100:27018,192.168.1.101:27018,192.168.1.102:27018”)
> sh.addShard(“fgedu-shard2/192.168.1.100:27028,192.168.1.101:27028,192.168.1.102:27028”)
# 6. 查看分片状态
> sh.status()
# 输出:
— Sharding Status —
sharding version: {
“_id”: 1,
“minCompatibleVersion”: 5,
“currentVersion”: 6,
“clusterId”: ObjectId(“60a7b8c9d0e1f2a3b4c5d6e7”)
}
shards:
{ “_id”: “fgedu-shard1”, “host”: “fgedu-shard1/192.168.1.100:27018,192.168.1.101:27018,192.168.1.102:27018”, “state”: 1 }
{ “_id”: “fgedu-shard2”, “host”: “fgedu-shard2/192.168.1.100:27028,192.168.1.101:27028,192.168.1.102:27028”, “state”: 1 }
active mongoses:
“4.4.0”: 1
autosplit:
Currently enabled: yes
balancer:
Currently enabled: yes
Currently running: no
Failed balancer rounds in last 5 attempts: 0
Migration Results for the last 24 hours:
No recent migrations
databases:
{ “_id”: “config”, “primary”: “config”, “partitioned”: true }
from MongoDB视频:www.itpux.com
4.2 分片键选择实战
分片键选择实战:
# 场景:为用户集合选择分片键
# 1. 分析查询模式
# 常用查询1:根据user_id查询用户信息
db.fgedu_users.find({ user_id: “USER001” })
# 常用查询2:根据注册时间查询用户
db.fgedu_users.find({ register_date: { $gte: ISODate(“2026-01-01”) } })
# 常用查询3:根据地区查询用户
db.fgedu_users.find({ region: “北京” })
# 2. 选择分片键
# 方案1:使用user_id进行哈希分片
# 优点:数据分布均匀,查询效率高
# 缺点:范围查询需要扫描所有分片
sh.shardCollection(“fgedudb.fgedu_users”, { user_id: “hashed” })
# 方案2:使用register_date进行范围分片
# 优点:支持范围查询
# 缺点:可能出现热点(新数据集中在最后一个分片)
sh.shardCollection(“fgedudb.fgedu_users”, { register_date: 1 })
# 方案3:使用复合分片键(region + user_id)
# 优点:支持区域查询,数据分布相对均匀
sh.shardCollection(“fgedudb.fgedu_users”, { region: 1, user_id: 1 })
# 3. 验证分片效果
# 插入测试数据
for (let i = 0; i < 10000; i++) {
db.fgedu_users.insertOne({
user_id: "USER" + i,
name: "用户" + i,
region: ["北京", "上海", "广州", "深圳"][i % 4],
register_date: new Date(2026, i % 12, i % 28 + 1)
})
}
# 查看数据分布
db.fgedu_users.getShardDistribution()
# 输出:
Shard fgedu-shard1 at fgedu-shard1/192.168.1.100:27018,192.168.1.101:27018,192.168.1.102:27018
data : 2.5MB docs : 5000 chunks : 2
estimated data per chunk : 1.25MB
estimated docs per chunk : 2500
Shard fgedu-shard2 at fgedu-shard2/192.168.1.100:27028,192.168.1.101:27028,192.168.1.102:27028
data : 2.5MB docs : 5000 chunks : 2
estimated data per chunk : 1.25MB
estimated docs per chunk : 2500
4.3 数据迁移实战
数据迁移实战:
# 场景:将数据从Shard1迁移到Shard2
# 1. 查看当前Chunk分布
use config
db.chunks.find({ ns: “fgedudb.fgedu_users” }).pretty()
# 输出:
{
“_id”: “fgedudb.fgedu_users-user_id_MinKey”,
“lastmod”: Timestamp(1, 0),
“lastmodEpoch”: ObjectId(“60a7b8c9d0e1f2a3b4c5d6e7”),
“ns”: “fgedudb.fgedu_users”,
“min”: { “user_id”: { “$minKey”: 1 } },
“max”: { “user_id”: NumberLong(“-4611686018427387902”) },
“shard”: “fgedu-shard1”
}
{
“_id”: “fgedudb.fgedu_users-user_id_-4611686018427387902”,
“lastmod”: Timestamp(1, 1),
“lastmodEpoch”: ObjectId(“60a7b8c9d0e1f2a3b4c5d6e7”),
“ns”: “fgedudb.fgedu_users”,
“min”: { “user_id”: NumberLong(“-4611686018427387902”) },
“max”: { “user_id”: { “$maxKey”: 1 } },
“shard”: “fgedu-shard2”
}
# 2. 手动迁移Chunk
sh.moveChunk(
“fgedudb.fgedu_users”,
{ user_id: MinKey },
“fgedu-shard2”
)
# 输出:
{ “millis”: 1500, “ok”: 1 }
# 3. 查看迁移后的分布
db.chunks.find({ ns: “fgedudb.fgedu_users” }).pretty()
# 4. 配置自动Balancer
# 开启Balancer
sh.startBalancer()
# 设置迁移窗口(只在凌晨2点到6点进行迁移)
use config
db.settings.update(
{ _id: “balancer” },
{ $set: { activeWindow: { start: “02:00”, stop: “06:00” } } },
{ upsert: true }
)
# 5. 查看Balancer状态
sh.isBalancerRunning()
# 输出:true
# 6. 查看数据分布
db.fgedu_users.getShardDistribution()
风哥提示:数据迁移会影响系统性能,建议在业务低峰期进行。
Part05-风哥经验总结与分享
5.1 分表分库最佳实践
风哥建议的分表分库最佳实践:
- 分片键选择:选择高基数、均匀分布、查询友好的字段作为分片键
- 避免热点:避免使用单调递增的字段作为分片键
- 预分片:对于已知的数据范围,可以提前进行分片
- 监控均衡:定期监控数据分布,确保均衡
- 迁移窗口:设置合理的迁移窗口,避免影响业务
- 索引优化:确保分片键上有索引
- 查询优化:尽量使用分片键进行查询,避免跨分片查询
- 容量规划:提前规划分片数量,避免频繁扩容
学习交流加群风哥QQ113257174
5.2 常见问题与解决方案
常见问题与解决方案:
- 问题:数据分布不均匀
- 解决方案:重新选择分片键,使用哈希分片,手动迁移Chunk
- 问题:出现热点分片
- 解决方案:避免使用单调递增字段,使用复合分片键
- 问题:跨分片查询性能差
- 解决方案:优化查询条件,使用分片键过滤,避免全表扫描
- 问题:Chunk迁移失败
- 解决方案:检查网络连接,确保目标分片有足够空间,检查权限
- 问题:Balancer无法启动
- 解决方案:检查Config Server状态,检查是否有未完成的迁移
更多视频教程www.fgedu.net.cn
注意事项
- 选择高基数、均匀分布、查询友好的字段作为分片键
- 避免使用单调递增的字段作为分片键
- 定期监控数据分布,确保均衡
- 设置合理的迁移窗口,避免影响业务
- 确保分片键上有索引
- 尽量使用分片键进行查询,避免跨分片查询
- 提前规划分片数量,避免频繁扩容
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
