我们提到过,MongoDB分片需要三个角色才能完成,一个是Config Server作为元数据存储服务器,一个是Mongos实例作为查询路由转发器,最后是MongoD实例用于存储实际用户数据。
一、启动实例
Config Server其实也是一个MongoDB实例,只不过我们通过启动时增加--configsvr选项将其功能专注于集群元数据的处理,每个Config Server都有一套完整且独立的集群元数据。在实际生产环境下,我们需要部署三个Config Server在不同的机器上,以提高可用性。在测试环境下,我们可以将三个Config Server部署在一台机器上,或者只部署一个Config Server,默认情况下Config Server的端口号为27019
[root@localhost bin]# mkdir -p /mongodb/data/configdb1
[root@localhost bin]# mkdir -p /mongodb/data/configdb2
[root@localhost bin]# mkdir -p /mongodb/data/configdb3
我们给每个Config Server单独设置一个配置文件,每个配置文件的configsvr选项都设置为true。由于在同一台机器上运行,我们将各Config Server的端口号依次为27119、27219、27319,数据库目录依次为configdb1、configdb2、configdb3,其中configsvr1如下:
fork = true
bind_ip = 127.0.0.1
port = 27119
quiet = true
dbpath = /mongodb/data/configdb1
logpath = /mongodb/log/mongod_configdb1.log
logappend = true
journal = true
configsvr=true
smallfiles = true
oplogSize = 128
[root@localhost bin]#./mongod --config /mongodb/mongodb.conf1
[root@localhost bin]#./mongod --config /mongodb/mongodb.conf2
[root@localhost bin]#./mongod --config /mongodb/mongodb.conf3
完成对Config Server的启动后,我们就可以启动mongos实例。mongos实例是一个轻量级的实例,不需要存储数据的目录,其默认端口号为27017。在启动mongos实例时,我们需要指定ConfigServer的IP和端口号。为了避免因Config Server IP地址变化导致MongoDB实例和Mongos实例重新启动,因此最好给每个Config Server设置一个DNS逻辑名,Mongos实例启动时直接使用Config Server的DNS逻辑名和端口号。我们给mongos实例设置的配置文件如下:
fork = true
bind_ip = 127.0.0.1
port = 27017
quiet = true
configdb= 127.0.0.1:27119,127.0.0.1:27219,127.0.0.1:27319
logpath = /mongodb/log/mongos.log
下面启动mongos实例和sharding实例,sharding实例启动和普通Mongod实例基本相同,只是多设置一个shardsvr为TRUE即可,带上该参数后mongod实例的默认端口号为27018。我们拟启动两个sharding实例,端口号依次为27118、27218。
[root@localhost bin]# ./mongos --config /mongodb/mongodb.s
[root@localhost bin]# ./mongod --config /mongodb/mongodb.sh1
[root@localhost bin]# ./mongod --config /mongodb/mongodb.sh2
二、初始化sharding
1、连接mongos实例,将两个sharding实例增加到集群中
[root@localhost bin]# ./mongo --port 27017
MongoDB shell version: 2.5.2
connecting to: 127.0.0.1:27017/test
mongos> sh.addShard( "127.0.0.1:27118" )sh.addShard( "127.0.0.1:27118" )
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> sh.addShard( "127.0.0.1:27218" )sh.addShard( "127.0.0.1:27218" )
{ "shardAdded" : "shard0001", "ok" : 1 }
2、使能test数据库以支持集群
mongos> sh.enableSharding("test")
{ "ok" : 1 }
3、使能people集合以支持分片
mongos> sh.shardCollection("test.people", {user_id:1})
{ "collectionsharded" : "test.people", "ok" : 1 }
三、观察集群状态
我们可以使用sh.status()查看集群的状态。下面的命令结果显示,在集群中有两个分片,两个数据库中只有Test数据库支持分片,其主分片是shard0000。主分片是指在分片开始之前,集合数据所在的位置。
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 3,
"minCompatibleVersion" : 3,
"currentVersion" : 4,
"clusterId" : ObjectId("5284bb99c8b3dd65fd2c3ea6")
}
shards:
{ "_id" : "shard0000", "host" : "127.0.0.1:27118" }
{ "_id" : "shard0001", "host" : "127.0.0.1:27218" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : true, "primary" : "shard0000" }
test.people
shard key: { "user_id" : 1 }
chunks:
shard0000 1
{ "user_id" : { "$minKey" : 1 } } -->> { "user_id" : { "$maxKey" : 1 } } on : shard0000 Timestamp(1, 0)
mongos>
由于people表中还没有数据,上面集群状态显示people集合只有一个Chunk。下面我们插入1万数据,观察一下Chunk分裂的过程。
use test
people = ["Marc", "Bill", "George", "Eliot", "Matt", "Trey", "Tracy", "Greg", "Steve", "Kristina"]
for(var i=0; i<10000; i++){
name = people[Math.floor(Math.random()*people.length)];
user_id = i;
boolean = [true, false][Math.floor(Math.random()*2)];
added_at = new Date();
number = Math.floor(Math.random()*10001);
db.people.save({"name":name, "user_id":user_id, "boolean":boolean,"added_at":added_at,"number":number });
}
在数据完成插入以后,我们再执行sh.status(),发现目前已经people集合拥有三个Chunk,shard0000拥有2个chunk,shard0001拥有1一个chunk。shard0000上,user_id从最小值到0为一个chunk,0到5739为另外一个chunk;shard0001,user_id从5739到最大值为一个chunk。
..............................
test.people
shard key: { "user_id" : 1 }
chunks:
shard0000 2
shard0001 1
{ "user_id" : { "$minKey" : 1 } } -->> { "user_id" : 0 } on : shard0000 Timestamp(2, 1)
{ "user_id" : 0 } -->> { "user_id" : 5739 } on : shard0000 Timestamp(1, 3)
{ "user_id" : 5739 } -->> { "user_id" : { "$maxKey" : 1 } } on : shard0001 Timestamp(2, 0)
我们继续向people集合插入数据,user_id从1万到100万,合计99万条记录。在插入完成后发现chunk分裂成了8个,shard0000和shard0001上各4个Chunk。
..................................
test.people
shard key: { "user_id" : 1 }
chunks:
shard0001 4
shard0000 4
{ "user_id" : { "$minKey" : 1 } } -->> { "user_id" : 0 } on : shard0001 Timestamp(8, 0)
{ "user_id" : 0 } -->> { "user_id" : 5739 } on : shard0000 Timestamp(8, 1)
{ "user_id" : 5739 } -->> { "user_id" : 188952 } on : shard0001 Timestamp(7, 1)
{ "user_id" : 188952 } -->> { "user_id" : 372157 } on : shard0000 Timestamp(4, 2)
{ "user_id" : 372157 } -->> { "user_id" : 555370 } on : shard0001 Timestamp(5, 2)
{ "user_id" : 555370 } -->> { "user_id" : 746356 } on : shard0001 Timestamp(6, 2)
{ "user_id" : 746356 } -->> { "user_id" : 929568 } on : shard0000 Timestamp(7, 2)
{ "user_id" : 929568 } -->> { "user_id" : { "$maxKey" : 1 } } on : shard0000 Timestamp(7, 3)
四、集群维护
1、迁移Config Server
一般集群拥有三个Config Server,当某一台出现故障时,我们可以进行在线迁移。在迁移过程中,如果不需要更改Config Server的主机名称,则步骤如下:
- 使用sh.setBalancerState(false)暂时停止后台的Balance活动,并使用sh.getBalancerState()确认
- 关闭拟迁移的Config Server,此时集群的配置数据变得只读,所有Chunk的分裂和平衡活动都被禁止,但是用户正常的数据插入是被允许的。
- 更改DNS解析条目,将原主机名称指向新的IP地址。
- 将老Config Server的DB目录复制到新Config Server的DB目录下。
- 在新机器上启动Config Server实例,并使用sh.setBalancerState(true)启动Chunk平衡。
如果涉及到的Config Server的主机名称发生变化,则需要一定的停机重启时间,步骤如下:
- 使用sh.setBalancerState(false)暂时停止后台的Balance活动,并使用sh.getBalancerState()确认
- 关闭拟迁移的Config Server,此时集群的配置数据变得只读,所有Chunk的分裂和平衡活动都被禁止,但是用户正常的数据插入是被允许的。
- 将老Config Server的DB目录复制到新Config Server的DB目录下。
- 在新机器上启动Config Server实例。
- 关闭现有的实例,包括Config Server实例,mongos实例和存放实际用户数据的集群实例
- 启动所有集群实例
- 更新mongos实例的configdb配置参数,并重新启动mongos实例
- 使用sh.setBalancerState(true)启动Chunk平衡
从上述过程可以看出,如果需要更改configdb的配置需要对整个集群进行停机,因此我们在规划时应尽可能采用DNS方式解析Config Server以提高集群的可用性。
2. 维护集群实例
需要将增加新集群实例到集群中时,我们需要首先启动拟新增加的集群实例,然后在mongo shell环境下执行sh.addShard()命令即可。
当我们需要从集群中删除一个集群实例时,一般步骤如下:
- 使用sh.setBalancerState(TRUE)启动Chunk平衡
- 找到准备删除的shard名字,例如我们拟删除mongodb0分片
- 执行命令removeShard,此命令将从该分片中移出所有Chunk到其他分片,如使用db.runCommand( { removeShard: "mongodb0" } )删除mongodb0分片。
- 使用removeShard不断观察迁移的进展情况,直到返回"remove shard completed successfully"消息。
- 使用sh.status()命令查看是否有文档是以该分片为主分片的,movePrimary命令迁移主分片,如db.runCommand( { movePrimary: "products", to: "mongodb1" })将products文档的主分片设置为mongodb1.