在生产环境中,通常情况使用副本集就够了(使用配置文件部署副本集可跳转:5.x 副本集部署,使用命令行部署副本集可参考这篇文章)。除非容量非常大,并发访问非常高,副本集已经无法正常提供服务时,才建议考虑使用分片。这一节内容就来聊聊 MongoDB 分片。
1 MongoDB 分片介绍
1.1 MongoDB 分片架构
MongoDB 分片架构图如下:
各个组件的作用:
- shard:存储数据,为了提高可用性和数据一致性,每个分片都是一个副本集。分片和分片之间的数据不重复。
- Router(或者mongos):与客户端相连,并将操作定向到适当的一个或多个分片。从 MongoDB 4.4 开始,mongos 开始支持 hedged reads 最大程度减少延迟。生产环境可配置多个 mongos 以实现高可用或者负载均衡。
- config Server:存储集群的元数据。该数据包含集群数据集到分片的映射。查询路由器使用此元数据将操作定向到特定的分片。
1.2 分片键
分片键是集合中每个文档中都存在的索引字段或索引复合字段,MongoDB将分片键值划分为多个块,并将这些块均匀地分布在各个分片上。要将分片键值划分为多个块,MongoDB使用基于范围的分区或基于哈希的分区。有基于范围的分片和基于哈希的分片。
从 MongoDB 4.2 开始,可以更新文档的分片键值,除非分片键字段是不可变 id 字段。
1.3 平衡
平衡器是管理数据块迁移的后台进程。平衡器可以从群集中的任何查询路由器运行。
当分片集合在集群中分布不均匀时,平衡器进程会将块从具有最多块数的分片迁移到具有最小块数的分片中,直到集群平衡。
1.4 从集群添加和删除分片
将分片添加到集群会导致不平衡,当 MongoDB立即开始将数据迁移到新地分片时,集群平衡可能需要一段时间.
删除分片时,平衡器将所有块从一共分片迁移到其他分片。迁移所有数据并更新元数据后,可以安全地删除分片。
2 MongoDB 分片集群部署
2.1 架构介绍
这次实验架构如下:
其中:
Hostname | IP |
---|---|
node1 | 192.168.150.232 |
node2 | 192.168.150.253 |
node3 | 192.168.150.123 |
MongoDB 版本采用的是:5.0.3。
2.2 部署 config server
第一台机器上:
代码语言:javascript复制mkdir /data/mongodb/config -p
mongod --configsvr --replSet config --dbpath /data/mongodb/config --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/config/mongod.log --port 27020 --fork
第二台机器上:
代码语言:javascript复制mkdir /data/mongodb/config -p
mongod --configsvr --replSet config --dbpath /data/mongodb/config --bind_ip localhost,192.168.150.253 --logpath /data/mongodb/config/mongod.log --port 27020 --fork
第三台机器上:
代码语言:javascript复制mkdir /data/mongodb/config -p
mongod --configsvr --replSet config --dbpath /data/mongodb/config --bind_ip localhost,192.168.150.123 --logpath /data/mongodb/config/mongod.log --port 27020 --fork
连接到其中一台:
代码语言:javascript复制mongosh --host 192.168.150.232 --port 27020
启动副本集:
代码语言:javascript复制rs.initiate(
{
_id: "config",
configsvr: true,
members: [
{ _id : 0, host : "192.168.150.232:27020" },
{ _id : 1, host : "192.168.150.253:27020" },
{ _id : 2, host : "192.168.150.123:27020" }
]
}
)
查看副本集状态:
代码语言:javascript复制rs.status()
注意以下信息:
代码语言:javascript复制......
members: [
{
_id: 0,
name: '192.168.150.232:27020',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 53,
......
},
{
_id: 1,
name: '192.168.150.253:27020',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 17,
......
},
{
_id: 2,
name: '192.168.150.123:27020',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 17,
......
}
],
......
有 1 个 PRIMARY 和 2 个 SECONDARY,并且 health 的值都为 1,说明集群创建正常。
2.3 MongoDB 实例启动
node1
代码语言:javascript复制mkdir /data/mongodb/shardtest01 -p
mkdir /data/mongodb/shardtest02 -p
mongod --shardsvr --replSet shardtest01 --dbpath /data/mongodb/shardtest01 --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/shardtest01/mongod.log --port 27001 --fork
mongod --shardsvr --replSet shardtest02 --dbpath /data/mongodb/shardtest02 --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/shardtest02/mongod.log --port 27002 --fork
node2
代码语言:javascript复制mkdir /data/mongodb/shardtest01 -p
mkdir /data/mongodb/shardtest02 -p
mongod --shardsvr --replSet shardtest01 --dbpath /data/mongodb/shardtest01 --bind_ip localhost,192.168.150.253 --logpath /data/mongodb/shardtest01/mongod.log --port 27001 --fork
mongod --shardsvr --replSet shardtest02 --dbpath /data/mongodb/shardtest02 --bind_ip localhost,192.168.150.253 --logpath /data/mongodb/shardtest02/mongod.log --port 27002 --fork
node3
代码语言:javascript复制mkdir /data/mongodb/shardtest01 -p
mkdir /data/mongodb/shardtest02 -p
mongod --shardsvr --replSet shardtest01 --dbpath /data/mongodb/shardtest01 --bind_ip localhost,192.168.150.123 --logpath /data/mongodb/shardtest01/mongod.log --port 27001 --fork
mongod --shardsvr --replSet shardtest02 --dbpath /data/mongodb/shardtest02 --bind_ip localhost,192.168.150.123 --logpath /data/mongodb/shardtest02/mongod.log --port 27002 --fork
2.4 创建分片副本集
创建第一个分片副本集
连接到其中一台:
代码语言:javascript复制mongosh --host 192.168.150.232 --port 27001
启动副本集:
代码语言:javascript复制rs.initiate(
{
_id: "shardtest01",
members: [
{ _id : 0, host : "192.168.150.232:27001" },
{ _id : 1, host : "192.168.150.253:27001" },
{ _id : 2, host : "192.168.150.123:27001" }
]
}
)
查看副本集:
代码语言:javascript复制rs.status()
判断是否正常跟上面聊到的 config server 副本集判断方法一样。
创建第二个分片副本集
连接到其中一台:
代码语言:javascript复制mongosh --host 192.168.150.232 --port 27002
启动副本集:
代码语言:javascript复制rs.initiate(
{
_id: "shardtest02",
members: [
{ _id : 0, host : "192.168.150.232:27002" },
{ _id : 1, host : "192.168.150.253:27002" },
{ _id : 2, host : "192.168.150.123:27002" }
]
}
)
查看副本集:
代码语言:javascript复制rs.status()
判断是否正常跟上面聊到的 config server 副本集判读方法一样。
2.5 启动 mongos
在其中一台机器上(这里选择的时:192.168.150.232)启动 mongos,启动 mongos 需要指定之前部署的 config 副本集。
代码语言:javascript复制mkdir /data/mongodb/mongos -p
mongos --configdb config/192.168.150.232:27020,192.168.150.232:27020,192.168.150.232:27020 --bind_ip localhost,192.168.150.232 --logpath /data/mongodb/mongos/mongos.log --fork
2.6 将分片副本集添加到集群
连接到分片集群:
代码语言:javascript复制mongosh --host 192.168.150.232 --port 27017
将分片副本集添加到集群:
代码语言:javascript复制sh.addShard( "shardtest01/192.168.150.232:27001,192.168.150.253:27001,192.168.150.123:27001")
sh.addShard( "shardtest02/192.168.150.232:27002,192.168.150.253:27002,192.168.150.123:27002")
2.7 创建分片表
为数据库启动分片
代码语言:javascript复制sh.enableSharding("martin")
对 collection 进行分片,这里是根据"_id"字段做 hash 分片,读者可根据自己实际情况进行分片,要注意的是,如果collection drop 掉重建了,需要重新创建 collection 分片规则。
代码语言:javascript复制sh.shardCollection("martin.userinfo", {_id: "hashed" } )
查看分片详情
代码语言:javascript复制sh.status();
可以看到如下结果:
代码语言:javascript复制......
shards
[
{
_id: 'shardtest01',
host: 'shardtest01/192.168.150.123:27001,192.168.150.232:27001,192.168.150.253:27001',
state: 1,
topologyTime: Timestamp({ t: 1649497030, i: 2 })
},
{
_id: 'shardtest02',
host: 'shardtest02/192.168.150.123:27002,192.168.150.232:27002,192.168.150.253:27002',
state: 1,
topologyTime: Timestamp({ t: 1649502644, i: 2 })
}
]
---
active mongoses
[ { '5.0.3': 1 } ]
......
databases
[
{
database: { _id: 'config', primary: 'config', partitioned: true },
collections: {
'config.system.sessions': {
shardKey: { _id: 1 },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'shardtest01', nChunks: 1022 },
{ shard: 'shardtest02', nChunks: 2 }
],
chunks: [
'too many chunks to print, use verbose if you want to force print'
],
tags: []
}
}
},
{
database: {
_id: 'martin',
primary: 'shardtest01',
partitioned: true,
version: {
uuid: UUID("b0ec23fa-2c9c-45ad-8ac1-eab27e4b24c3"),
timestamp: Timestamp({ t: 1649497150, i: 1 }),
lastMod: 1
}
},
collections: {
'martin.userinfo': {
shardKey: { _id: 'hashed' },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'shardtest01', nChunks: 1 },
{ shard: 'shardtest02', nChunks: 1 }
],
chunks: [
{ min: { _id: MinKey() }, max: { _id: Long("0") }, 'on shard': 'shardtest02', 'last modified': Timestamp({ t: 2, i: 0 }) },
{ min: { _id: Long("0") }, max: { _id: MaxKey() }, 'on shard': 'shardtest01', 'last modified': Timestamp({ t: 2, i: 1 }) }
],
tags: []
}
}
}
]
其中:
- database._id 表示开启分片的库;
- database.primary 表示主 shard;
- database.partitioned 表示这个库是否开启分片,true 表示开启;
- collections 中表示分片的表;
- collections.shardKey 表示分片健;
- collections.balancing 为 true 表示平衡;
- collections.chunks 数据分布情况。
3 数据分布测试
3.1 写入数据
登录 mongos
代码语言:javascript复制mongo --host 192.168.150.232 --port 27017 martin
往分片表 userinfo 写入数据:
代码语言:javascript复制for (var i=1; i<=10; i ) db.userinfo.save({userid:i,username:'a'});
3.2 查看数据分布
登录 shardtest01:
代码语言:javascript复制mongo --host 192.168.150.232 --port 27001 martin
查看数据:
代码语言:javascript复制shardtest01:PRIMARY> db.userinfo.find()
{ "_id" : ObjectId("62524b9da1e27b1723e77844"), "userid" : 1, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77847"), "userid" : 4, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77848"), "userid" : 5, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784a"), "userid" : 7, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784c"), "userid" : 9, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784d"), "userid" : 10, "username" : "a" }
登录 shardtest02:
代码语言:javascript复制mongo --host 192.168.150.232 --port 27002 martin
查看数据:
代码语言:javascript复制shardtest02:PRIMARY> db.userinfo.find()
{ "_id" : ObjectId("62524b9da1e27b1723e77845"), "userid" : 2, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77846"), "userid" : 3, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e77849"), "userid" : 6, "username" : "a" }
{ "_id" : ObjectId("62524b9da1e27b1723e7784b"), "userid" : 8, "username" : "a" }
可以看到数据分布在两个分片中。
4 参考资料
官方文档 Deploy a Sharded Cluster:https://www.mongodb.com/docs/manual/tutorial/deploy-shard-cluster/