目录[-]
为何需要水平分片
1 减少单机请求数,将单机负载,提高总负载
2 减少单机的存储空间,提高总存空间。
下图一目了然:
mongodb sharding 服务器架构
简单注解:
1 mongos 路由进程, 应用程序接入mongos再查询到具体分片。
2 config server 路由表服务。 每一台都具有全部chunk的路由信息。
3 shard为数据存储分片。 每一片都可以是复制集(replica set)。
如何部署分片集群
step 1 启动config server
1 | mkdir /data/configdb |
2 | mongod --configsvr --dbpath /data/configdb --port 27019 |
step 2 启动mongos
1 | mongos --configdb cfg0.example.net:27019,cfg1.example.net:27019,cfg2.example.net:27019 |
分片就是普通的mongod
1 | mongod --dbpath <path> --port <port> |
step4 在mongos添加分片
用mongo 连接上mongos, 然后通过Mongo命令行输入:
添加非replica set作为分片:
sh.addShard( "mongodb0.example.net:27017" )
添加replica set作为分片:
sh.addShard( "rs1/mongodb0.example.net:27017" )step5 对某个数据库启用分片
sh.enableSharding("这里只是标识这个数据库可以启用分片,但实际上并没有进行分片。")
step6 对collection进行分片
分片时需要指定分片的key, 语法为
sh.shardCollection("例子为:. ", shard-key-pattern)
sh.shardCollection("records.people", { "zipcode": 1, "name": 1 } )
sh.shardCollection("people.addresses", { "state": 1, "_id": 1 } )
sh.shardCollection("assets.chairs", { "type": 1, "_id": 1 } )
1 | db.alerts.ensureIndex( { _id : "hashed" } ) |
2 | sh.shardCollection( "events.alerts" , { "_id" : "hashed" } ) |
最后一个为hash sharded key。 hash sharded key是为了解决某些情况下sharded key的 write scaling的问题。
如何选择shard key
1 shard key需要有高的cardinality 。 也就是shard key需要拥有很多不同的值。 便于数据的切分和迁移。
2 尽量与应用程序融合。让mongos面对查询时可以直接定位到某个shard。
3 具有随机性。这是为了不会让某段时间内的insert请求全部集中到某个单独的分片上,造成单片的写速度成为整个集群的瓶颈。用objectId作为shard key时会发生随机性差情况。 ObjectId实际上由进程ID+TIMESTAMP + 其他因素组成, 所以一段时间内的timestamp会相对集中。
不过随机性高会有一个副作用,就是query isolation性比较差。
可用hash key增加随机性。
如何查看shard信息
登上mongos
sh.status()或者需要看详细一点
sh.status({verbose:true})
01 | Sharding Status --- |
02 | sharding version: { "_id" : 1, "version" : 3 } |
03 | shards: |
04 | { "_id" : "shard0000" , "host" : "m0.example.net:30001" } |
05 | { "_id" : "shard0001" , "host" : "m3.example2.net:50000" } |
06 | databases: |
07 | { "_id" : "admin" , "partitioned" : false , "primary" : "config" } |
08 | { "_id" : "contacts" , "partitioned" : true , "primary" : "shard0000" } |
09 | foo.contacts |
10 | shard key: { "zip" : 1 } |
11 | chunks: |
12 | shard0001 2 |
13 | shard0002 3 |
14 | shard0000 2 |
15 | { "zip" : { "$minKey" : 1 } } -->> { "zip" : 56000 } on : shard0001 { "t" : 2, "i" : 0 } |
16 | { "zip" : 56000 } -->> { "zip" : 56800 } on : shard0002 { "t" : 3, "i" : 4 } |
17 | { "zip" : 56800 } -->> { "zip" : 57088 } on : shard0002 { "t" : 4, "i" : 2 } |
18 | { "zip" : 57088 } -->> { "zip" : 57500 } on : shard0002 { "t" : 4, "i" : 3 } |
19 | { "zip" : 57500 } -->> { "zip" : 58140 } on : shard0001 { "t" : 4, "i" : 0 } |
20 | { "zip" : 58140 } -->> { "zip" : 59000 } on : shard0000 { "t" : 4, "i" : 1 } |
21 | { "zip" : 59000 } -->> { "zip" : { "$maxKey" : 1 } } on : shard0000 { "t" : 3, "i" : 3 } |
22 | { "_id" : "test" , "partitioned" : false , "primary" : "shard0000" } |
备份cluster meta information
Step1 disable balance process. 连接上Mongos
sh.setBalancerState(false)Step2 关闭config server
Step3 备份数据文件夹
Step4 重启config server
Step5 enable balance process.
sh.setBalancerState(false)
查看balance 状态
可以通过下面的命令来查看当前的balance进程状态。先连接到任意一台mongos
1 | use config |
2 | db.locks. find ( { _id : "balancer" } ).pretty() |
3 | { "_id" : "balancer" , |
4 | "process" : "mongos0.example.net:1292810611:1804289383" , |
5 | "state" : 2, |
6 | "ts" : ObjectId( "4d0f872630c42d1978be8a2e" ), |
7 | "when" : "Mon Dec 20 2010 11:41:10 GMT-0500 (EST)" , |
8 | "who" : "mongos0.example.net:1292810611:1804289383:Balancer:846930886" , |
9 | "why" : "doing balance round" } |
state=2 表示正在进行balance, 在2.0版本之前这个值是1
配置balance时间窗口
可以通过balance时间窗口指定在一天之内的某段时间之内可以进行balance, 其他时间不得进行balance。
先连接到任意一台mongos
1 | use config |
2 | db.settings.update({ _id : "balancer" }, { $ set : { activeWindow : { start : "23:00" , stop : "6:00" } } }, true ) |
这个设置让只有从23:00到6:00之间可以进行balance。
也可以取消时间窗口设置:
1 | use config |
2 | db.settings.update({ _id : "balancer" }, { $ unset : { activeWindow : true } }) |
修改chunk size
这是一个全局的参数。 默认是64MB。
小的chunk会让不同的shard数据量更均衡。 但会导致更多的Migration。
大的chunk会减少migration。不同的shard数据量不均衡。
这样修改chunk size。先连接上任意mongos
1 | db.settings.save( { _id: "chunksize" , value: <size> } ) |
单位是MB
何时会自动balance
每个mongos进程都可能发动balance。
一次只会有一个balance跑。 这是因为需要竞争这个锁:
1 | db.locks. find ( { _id : "balancer" } ) |
balance一次只会迁移一个chunk。
只有chunk最多的shard的chunk数目减去chunk最少的shard的chunk数目超过treshhold时才开始migration。
Number of Chunks | Migration Threshold |
Fewer than 20 | 2 |
21-80 | 4 |
Greater than 80 | 8 |
一旦balancer开始行动起来,只有当任意两个shard的chunk数量小于2或者是migration失败才会停止。
设置分片上最大的存储容量
有两种方式,第一种在添加分片时候用maxSize参数指定:
db.runCommand( { addshard : "example.net:34008", maxSize : 125 } )第二种方式可以在运行中修改设定:
1 | use config |
2 | db.shards.update( { _id : "shard0000" }, { $ set : { maxSize : 250 } } ) |
删除分片
连接上任意一台mongos
STEP1 确认balancer已经打开。
STEP2 运行命令:
db.runCommand( { removeShard: "mongodb0" } )mongodb0是需要删除的分片的名字。这时balancer进程会开始把要删除掉的分片上的数据往别的分片上迁移。
STEP3 查看是否删除完
还是运行上面那条removeShard命令
如果还未删除完数据则返回:
{ msg: "draining ongoing" , state: "ongoing" , remaining: { chunks: NumberLong(42), dbs : NumberLong(1) }, ok: 1 }STEP4 删除unsharded data
有一些分片上保存上一些unsharded data, 需要迁移到其他分片上:
可以用sh.status()查看分片上是否有unsharded data。
如果有则显示:
{ "_id" : "products", "partitioned" : true, "primary" : "mongodb0" }用下面的命令迁移:
db.runCommand( { movePrimary: "products", to: "mongodb1" })只有全部迁移完上面的命令才会返回:
{ "primary" : "mongodb1", "ok" : 1 }STEP5 最后运行命令
db.runCommand( { removeShard: "mongodb0" } )
手动迁移分片
一般情况下你不需要这么做,只有当一些特殊情况发生时,比如:
1 预分配空的集合时
2 在balancing时间窗之外
手动迁移的方法:
01 | chunks: |
02 | shard0000 2 |
03 | shard0001 2 |
04 | { "zipcode" : { "$minKey" : 1 } } -->> { "zipcode" : 10001 } on : shard0000 Timestamp(6, 0) |
05 | { "zipcode" : 10001 } -->> { "zipcode" : 23772 } on : shard0001 Timestamp(6, 1) |
06 | { "zipcode" : 23772 } -->> { "zipcode" : 588377 } on : shard0001 Timestamp(3, 2) |
07 | { "zipcode" : 588377 } -->> { "zipcode" : { "$maxKey" : 1 } } on : shard0000 Timestamp(5, 1) |
08 | mongos> db.adminCommand({moveChunk: "contact.people" , find :{zipcode:10003}, to: "192.168.1.135:20002" }) |
09 | { "millis" : 2207, "ok" : 1 } |
10 | mongos> sh.status() |
11 | --- Sharding Status --- |
12 | sharding version: { |
13 | "_id" : 1, |
14 | "version" : 3, |
15 | "minCompatibleVersion" : 3, |
16 | "currentVersion" : 4, |
17 | "clusterId" : ObjectId( "52ece49ae6ab22400d937891" ) |
18 | } |
19 | shards: |
20 | { "_id" : "shard0000" , "host" : "192.168.1.135:20002" } |
21 | { "_id" : "shard0001" , "host" : "192.168.1.135:20003" } |
22 | databases: |
23 | { "_id" : "admin" , "partitioned" : false , "primary" : "config" } |
24 | { "_id" : "test" , "partitioned" : false , "primary" : "shard0000" } |
25 | { "_id" : "contact" , "partitioned" : true , "primary" : "shard0000" } |
26 | contact.people |
27 | shard key: { "zipcode" : 1 } |
28 | chunks: |
29 | shard0000 3 |
30 | shard0001 1 |
31 | { "zipcode" : { "$minKey" : 1 } } -->> { "zipcode" : 10001 } on : shard0000 Timestamp(6, 0) |
32 | { "zipcode" : 10001 } -->> { "zipcode" : 23772 } on : shard0000 Timestamp(7, 0) |
33 | { "zipcode" : 23772 } -->> { "zipcode" : 588377 } on : shard0001 Timestamp(7, 1) |
34 | { "zipcode" : 588377 } -->> { "zipcode" : { "$maxKey" : 1 } } on : shard0000 Timestamp(5, 1) |
35 |
36 | mongos> |
预分配空chunk
这是一种提高写效率的方法。相当于在写入真实数据之前,就分配好了数据桶,然后再对号入座。省去了创建chunk和split的时间。
实际上使用的是split命令:
db.runCommand( { split : "myapp.users" , middle : { email : prefix } } );myapp.users 是 collection的名字。
middle参数是split的点。
split命令如下:
db.adminCommand( { split:find 表示查找到的记录进行分裂. , } )
bounds是指定[low, up]分裂
middle是指定分裂的点。
一个预分配chunk的例子如下:
1 | for ( var x=97; x<97+26; x++ ){ |
2 | for ( var y=97; y<97+26; y+=6 ) { |
3 | var prefix = String.fromCharCode(x) + String.fromCharCode(y); |
4 | db.runCommand( { split : "myapp.users" , middle : { email : prefix } } ); |
5 | } |
6 | } |
这个预分配的目的是字母顺序有一定间隔的email, 分配到不同的chunk里。
例如aa-ag到一个chunk
ag-am到一个chunk
预分配的结果如下:1 | { "email" : { "$minKey" : 1 } } -->> { "email" : "aa" } on : shard0001 Timestamp(2, 0) |
2 | { "email" : "aa" } -->> { "email" : "ag" } on : shard0001 Timestamp(3, 0) |
3 | { "email" : "ag" } -->> { "email" : "am" } on : shard0001 Timestamp(4, 0) |
4 | { "email" : "am" } -->> { "email" : "as" } on : shard0001 Timestamp(5, 0) |
5 | { "email" : "as" } -->> { "email" : "ay" } on : shard0001 Timestamp(6, 0) |
6 | ... |
1 | { "email" : "zm" } -->> { "email" : "zs" } on : shard0000 Timestamp(1, 257) |
2 | { "email" : "zs" } -->> { "email" : "zy" } on : shard0000 Timestamp(1, 259) |
3 | { "email" : "zy" } -->> { "email" : { "$maxKey" : 1 } } on : shard0000 Timestamp(1, 260) |
如何删除在sharding中删除复制集replica set的成员
假设sharding的分片是复制集,需要删除某个复制集的某个成员。
只要在复制集的设置中删除该成员即可,不需要在mongos中删除。mongos会自动同步这个配置。
例如 sharding cluster中有这个分片:
{ "_id" : "rs3", "host" : "rs3/192.168.1.5:30003,192.168.1.6:30003" }
需要删除192.168.1.6:30003这个成员。
只需要:
step 1: 在192.168.1.6:30003上运行关闭mongod
step 2:在rs3的primary的成员192.168.1.5:30003上执行
rs.remove("192.168.1.6:30003")
如何关闭/打开balancer
关闭
sh.setBalancerState(false)打开
sh.setBalancerState(true)查看是否打开:
sh.getBalancerState()
新添加的分片始终不进行数据同步的问题
如果sharding cluster中新添加的分片始终不进行数据migration, 并出现类似日志:
migrate commit waiting for 2 slaves for
则需要重启该分片的mongod进程。
特别需要注意的是,如果某mongod进程是一个replica set的primary, 并且该replica set上只有一个mongod, 那么不能用db.shutdownServer()的方法关闭。 会报下面的错误:
no secondary is within 10 seconds of the primary,
需要用下面的命令关闭:
db.adminCommand({ shutdown : 1, force : true})