目录
大部分摘自《MongoDB大数据处理权威指南》(第3版)。
使用MongoDB时,水平数据分割是唯一可采用的方式。MongoDB使用一种唯一的方法用于分片,由MongoS路径进程管理数据的分割,并将请求路由到必须的分片服务器。如果查询需要访问多个分片中的数据,MongoS将管理从多个分片获取数据并将数据合并成单个游标的过程。
分片需求:
- 具有将数据平均分散到所有分片的能力。
- 以容错方式存储分片数据的能力。
- 在系统运行时添加或删除分片的能力。
一、架构
MongoDB使用代理机制实现分片(如图1所示);其中的mongos守护进程将作为多个基于mongod的分片服务器的控制器。当应用连接到mongos时,将把这些分片服务器当作单个MongoDB数据库服务器;此后,应用将把它的所有命令(例如更新、查询和删除)都发送到mongos进程。
进程mongos负责管理应用发送到MongoDB服务器的所有命令,并且该守护进程将重新发送跨多个分片的查询到多个服务器,再将结果聚集在一起。
MongoDB在集合级别实现分片,而不是数据库级别。在许多系统中,只有一个或两个集合可以增长到需要使用分片的地步。因此应该理智地使用分片;如果不需要的话,就不要为较小的集合增加管理分布数据的开销。
分片系统使用分片键将数据映射到块,块是文档键的逻辑连续范围。每个块标志着分片键值特定连续范围内的许多文档;这些值使mongos控制器可以快速找到包含它所需的文档的块。然后MongoDB分片系统将把块存储在可用的分片系统中;配置服务器将记录每个块存储的分片服务器的位置。这是分片实现的一个重要特性,因为通过它可以从集合中添加和删除分片,而不需要备份和恢复数据。
当在集群中添加新的分片时,该系统将会把许多块迁移到新的服务器集合中,从而平均地分散数据。类似地,从集群中删除分片时,分片控制器将会从即将离线的分片中抽取所有的块,并重新将它们分散到剩下的分片服务器中。
MongoDB的分片设置还需要存储分片服务器的配置,以及集群中每个分片服务器的信息。为了支持该功能,需要使用一台称为配置服务器的MongoDB服务器;该服务器实例是一个以特殊角色运行的mongod服务器。正如之前解释的,配置服务器还可以用作目录,通过它可以找到每个块的位置。在集群中可以具有1台(开发)或3台(生产)配置服务器。推荐在生产环境中使用3台配置服务器,因为配置服务器的崩溃将意味着无法决定分片数据存储在哪个分片中。
可以将多个不同服务的实例添加到同一服务器中。图2显示了一个完全冗余的分片系统,它将为分片存储和配置服务器使用副本集,并且使用一组mongos管理集群。它还显示了如何将这些服务以密集的方式运行在3台物理服务器中。
二、配置
测试环境:
分片控制器(mongos):hdp4:27017
配置服务器(mongod):hdp3:27017
分片0(mongod):hdp2:27017
分片1(mongod):hdp1:27017
1. 启动配置服务器
配置文件/home/mongodb/mongodb-4.0.2/mongodb.conf内容如下:
logpath = /home/mongodb/mongodb-4.0.2/data/mongodb.log
pidfilepath = /home/mongodb/mongodb-4.0.2/data/mongodb.pid
dbpath = /home/mongodb/mongodb-4.0.2/data/
bind_ip_all = true
configsvr = true
replSet = configs
port = 27017
其中configsvr参数指定该服务器为分片的配置服务器,replSet指定副本集名称为configs。
启动实例:
mongod -f /home/mongodb/mongodb-4.0.2/mongodb.conf &
副本集初始化,并向副本集中添加服务器:
$ mongo
> rs.initiate();
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "hdp3:27017",
"ok" : 1,
"operationTime" : Timestamp(1539908963, 1),
"$gleStats" : {
"lastOpTime" : Timestamp(1539908963, 1),
"electionId" : ObjectId("000000000000000000000000")
},
"lastCommittedOpTime" : Timestamp(0, 0),
"$clusterTime" : {
"clusterTime" : Timestamp(1539908963, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
configs:SECONDARY>
2. 启动分片控制器
配置文件/home/mongodb/mongodb-4.0.2/mongodb.conf内容如下:
logpath = /home/mongodb/mongodb-4.0.2/data/mongodb.log
pidfilepath = /home/mongodb/mongodb-4.0.2/data/mongodb.pid
bind_ip_all = true
configdb = configs/hdp3:27017
其中configdb参数指定配置服务器,格式为“副本集名称/配置服务器主机名:端口”
启动实例:
mongos -f /home/mongodb/mongodb-4.0.2/mongodb.conf &
3. 启动两个分片服务器
配置文件/home/mongodb/mongodb-4.0.2/mongodb.conf内容如下:
logpath = /home/mongodb/mongodb-4.0.2/data/mongodb.log
pidfilepath = /home/mongodb/mongodb-4.0.2/data/mongodb.pid
dbpath = /home/mongodb/mongodb-4.0.2/data/
bind_ip_all = true
port=27017
shardsvr=true
分别在hdp2、hdp1上启动分片实例:
mongod -f /home/mongodb/mongodb-4.0.2/mongodb.conf &
4. 在分片控制器中添加分片
$ mongo hdp4:27017
mongos> use admin;
switched to db admin
mongos> sh.addShard("hdp2:27017");
{
"shardAdded" : "shard0000",
"ok" : 1,
"operationTime" : Timestamp(1539909528, 4),
"$clusterTime" : {
"clusterTime" : Timestamp(1539909528, 4),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos> sh.addShard("hdp1:27017");
{
"shardAdded" : "shard0001",
"ok" : 1,
"operationTime" : Timestamp(1539909542, 2),
"$clusterTime" : {
"clusterTime" : Timestamp(1539909542, 2),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos>
两台分片服务器现在已经被激活,下面检查分片服务器的状态:
mongos> sh.status();
--- Sharding Status ---
sharding version: {
"_id" : 1,
"minCompatibleVersion" : 5,
"currentVersion" : 6,
"clusterId" : ObjectId("5bc92565577f5a97a8441de1")
}
shards:
{ "_id" : "shard0000", "host" : "hdp2:27017", "state" : 1 }
{ "_id" : "shard0001", "host" : "hdp1:27017", "state" : 1 }
active mongoses:
"4.0.2" : 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 }
mongos>
现在分片环境已经可以正常运行,但还没有分片数据。
5. 设置块大小
$ mongo hdp4:27017
mongos> use config;
switched to db config
mongos> db.settings.save({"_id":"chunksize","value":1});
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "chunksize" })
mongos>
设置块大小为1M是方便实验,不然就需要插入大量数据。
6. 创建分片集合
创建一个名为testdb的数据库,然后在该数据库中激活一个名为testcollection的集合,赋予它一个名为testkey的参数,用作分片键:
$ mongo hdp4:27017
mongos> use admin;
switched to db admin
mongos> sh.enableSharding("testdb");
{
"ok" : 1,
"operationTime" : Timestamp(1539914273, 5),
"$clusterTime" : {
"clusterTime" : Timestamp(1539914273, 5),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos> show dbs
admin 0.000GB
config 0.001GB
mongos> use testdb;
switched to db testdb
mongos> db.testcollection.createIndex({"testkey":1});
{
"raw" : {
"hdp1:27017" : {
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
},
"ok" : 1,
"operationTime" : Timestamp(1539914351, 4),
"$clusterTime" : {
"clusterTime" : Timestamp(1539914351, 4),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos> sh.shardCollection("testdb.testcollection",{"testkey":1});
{
"collectionsharded" : "testdb.testcollection",
"collectionUUID" : UUID("587f9db3-6217-4d17-a5a4-be128aed58d8"),
"ok" : 1,
"operationTime" : Timestamp(1539914504, 8),
"$clusterTime" : {
"clusterTime" : Timestamp(1539914504, 8),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos>
7. 向分片添加数据
连接控制器:
mongo hdp4:27017
插入100000条数据:
mongos> use testdb;
switched to db testdb
mongos> for(i=1;i<=100000;i++){db.testcollection.insert({"testkey":i,"name":"jack"+i})};
WriteResult({ "nInserted" : 1 })
mongos> db.testcollection.count();
100000
mongos>
连接分片查看每个分片的数据量:
mongo hdp2:27017
> use testdb;
switched to db testdb
> db.testcollection.count();
38399
mongo hdp1:27017
> use testdb;
switched to db testdb
> db.testcollection.count();
61601
注意,在每台分片服务器中可能看到不同的文档数目,这取决于检查每台分片服务器的时间。mongos实例开始会在一个分片中初始化所有的块,但随着时间的推移,将对数据集中的数据进行调整,通过移动块的方式将数据平均地分布到所有分片服务器中。因此,指定分片服务器中的记录数可能会不断变化。
三、维护
1. 添加新分片
(1)创建新的分片服务器
创建新mongod实例hdp4:27018,配置文件/home/mongodb/mongodb-4.0.2/mongodb1.conf内容如下:
logpath = /home/mongodb/mongodb-4.0.2/data1/mongodb.log
pidfilepath = /home/mongodb/mongodb-4.0.2/data1/mongodb.pid
dbpath = /home/mongodb/mongodb-4.0.2/data1/
bind_ip_all = true
port=27018
shardsvr=true
分别在hdp4上启动分片实例:
mongod -f /home/mongodb/mongodb-4.0.2/mongodb1.conf &
(2)向集群中添加新的分片
$ mongo hdp4:27017
mongos> use admin;
switched to db admin
mongos> sh.addShard("hdp4:27018");
{
"shardAdded" : "shard0002",
"ok" : 1,
"operationTime" : Timestamp(1539918006, 3),
"$clusterTime" : {
"clusterTime" : Timestamp(1539918006, 3),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos>
(3)查询分片状态
mongos> sh.status();
--- Sharding Status ---
sharding version: {
"_id" : 1,
"minCompatibleVersion" : 5,
"currentVersion" : 6,
"clusterId" : ObjectId("5bc9389c8d2765f26c37e01d")
}
shards:
{ "_id" : "shard0000", "host" : "hdp2:27017", "state" : 1 }
{ "_id" : "shard0001", "host" : "hdp1:27017", "state" : 1 }
{ "_id" : "shard0002", "host" : "hdp4:27018", "state" : 1 }
active mongoses:
"4.0.2" : 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:
8 : Success
databases:
{ "_id" : "config", "primary" : "config", "partitioned" : true }
config.system.sessions
shard key: { "_id" : 1 }
unique: false
balancing: true
chunks:
shard0000 1
{ "_id" : { "$minKey" : 1 } } -->> { "_id" : { "$maxKey" : 1 } } on : shard0000 Timestamp(1, 0)
{ "_id" : "testdb", "primary" : "shard0001", "partitioned" : true, "version" : { "uuid" : UUID("89e5f744-afca-434e-b94b-c700114f8795"), "lastMod" : 1 } }
testdb.testcollection
shard key: { "testkey" : 1 }
unique: false
balancing: true
chunks:
shard0000 4
shard0001 4
shard0002 3
{ "testkey" : { "$minKey" : 1 } } -->> { "testkey" : 2 } on : shard0002 Timestamp(9, 0)
{ "testkey" : 2 } -->> { "testkey" : 18080 } on : shard0002 Timestamp(7, 0)
{ "testkey" : 18080 } -->> { "testkey" : 27119 } on : shard0002 Timestamp(8, 0)
{ "testkey" : 27119 } -->> { "testkey" : 38587 } on : shard0001 Timestamp(7, 1)
{ "testkey" : 38587 } -->> { "testkey" : 47626 } on : shard0000 Timestamp(6, 1)
{ "testkey" : 47626 } -->> { "testkey" : 57786 } on : shard0000 Timestamp(9, 1)
{ "testkey" : 57786 } -->> { "testkey" : 66825 } on : shard0001 Timestamp(8, 1)
{ "testkey" : 66825 } -->> { "testkey" : 76985 } on : shard0001 Timestamp(4, 3)
{ "testkey" : 76985 } -->> { "testkey" : 86024 } on : shard0000 Timestamp(5, 2)
{ "testkey" : 86024 } -->> { "testkey" : 96184 } on : shard0000 Timestamp(5, 3)
{ "testkey" : 96184 } -->> { "testkey" : { "$maxKey" : 1 } } on : shard0001 Timestamp(6, 0)
mongos>
查询三个分片的记录数:
$ mongo hdp1:27017
> use testdb;
switched to db testdb
> db.testcollection.count();
34484
>
$ mongo hdp2:27017
> use testdb;
switched to db testdb
> db.testcollection.count();
38398
>
$ mongo hdp4:27018
> use testdb;
switched to db testdb
> db.testcollection.count();
27118
>
分片系统将数据重新平均分布到扩展后的集群中。随着时间的推移,分片系统将从shard0和shard1存储服务器中迁移出一些块,从而将数据平均分布在组成集群的三台服务器中。该过程将自动发生,即使testcollection集合中没有新的数据插入,也会执行。在此情况下,mongos分片控制器将移动一些块到新的服务器,然后将它们注册到配置服务器中。
2. 删除分片
(1)删除分片
mongo hdp4:27017
mongos> use admin;
switched to db admin
mongos> db.runCommand({removeShard : "hdp4:27018"});
{
"msg" : "draining started successfully",
"state" : "started",
"shard" : "shard0002",
"note" : "you need to drop or movePrimary these databases",
"dbsToMove" : [ ],
"ok" : 1,
"operationTime" : Timestamp(1539919362, 2),
"$clusterTime" : {
"clusterTime" : Timestamp(1539919362, 2),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos>
命令removeShard返回一条信息,表示移除已经启动。另外还表示mongos已经开始重新将目标分片服务器中的块移到集群中的其它分片服务器。该过程被称为清空分片服务器。还列出了清空过程中不能移出分片服务器的数据库,这些都在dbsToMove数组中。
(2)验证
为验证removeShard命令是否成功,可使用listshards确认目标分片服务器是否已经从集群中移除。例如,下面的输出显示之前创建的服务器shard2已经不在shards数组中:
mongo hdp4:27017
mongos> use admin;
switched to db admin
mongos> db.runCommand({listshards:1});
{
"shards" : [
{
"_id" : "shard0000",
"host" : "hdp2:27017",
"state" : 1
},
{
"_id" : "shard0001",
"host" : "hdp1:27017",
"state" : 1
}
],
"ok" : 1,
"operationTime" : Timestamp(1539919761, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1539919761, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
mongos>
查询三个服务器的记录数:
$ mongo hdp1:27017
> use testdb;
switched to db testdb
> db.testcollection.count();
43524
>
$ mongo hdp2:27017
> use testdb;
switched to db testdb
> db.testcollection.count();
56476
>
$ mongo hdp4:27018
> use testdb;
switched to db testdb
> db.testcollection.count();
0
>
此时可以终止shard2 mongod进程并删除它的存储文件,因为它的数据已经被迁移到其它服务器中。