为何需要水平分片

1 减少单机请求数,将单机负载,提高总负载

2 减少单机的存储空间,提高总存空间。

下图一目了然:

MongoDB水平分片集群学习笔记_复制集



mongodb sharding 服务器架构

MongoDB水平分片集群学习笔记_.net_02

简单注解:

1 mongos 路由进程, 应用程序接入mongos再查询到具体分片。

2 config server 路由表服务。 每一台都具有全部chunk的路由信息。

3 shard为数据存储分片。 每一片都可以是复制集(replica set)。



如何部署分片集群

step 1 启动config server


mkdir           /data/configdb         


          mongod --configsvr --dbpath           /data/configdb           --port 27019

正式生产环境一般启动3个config server。 启动3个是为了做热备。


step 2 启动mongos


mongos --configdb cfg0.example.net:27019,cfg1.example.net:27019,cfg2.example.net:27019

step3 启动分片mongod


分片就是普通的mongod


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("<database>")

这里只是标识这个数据库可以启用分片,但实际上并没有进行分片。


step6 对collection进行分片

分片时需要指定分片的key, 语法为

sh.shardCollection("<database>.<collection>", 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 } )


db.alerts.ensureIndex( { _id :           "hashed"           } )         


          sh.shardCollection(          "events.alerts"          , {           "_id"          :           "hashed"           } )



最后一个为hash sharded key。 hash sharded key是为了解决某些情况下sharded key的 write scaling的问题。


如何选择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})


Sharding Status ---         


                    sharding version: {           "_id"           : 1,           "version"           : 3 }         


                    shards:         


                    {            "_id"           :           "shard0000"          ,            "host"           :           "m0.example.net:30001"           }         


                    {            "_id"           :           "shard0001"          ,            "host"           :           "m3.example2.net:50000"           }         


                    databases:         


                    {            "_id"           :           "admin"          ,            "partitioned"           :           false          ,            "primary"           :           "config"           }         


                    {            "_id"           :           "contacts"          ,            "partitioned"           :           true          ,            "primary"           :           "shard0000"           }         


                    foo.contacts         


                    shard key: {           "zip"           : 1 }         


                    chunks:         


                    shard0001    2         


                    shard0002    3         


                    shard0000    2         


                    {           "zip"           : {           "$minKey"           : 1 } } -->> {           "zip"           : 56000 } on : shard0001 {           "t"           : 2,           "i"           : 0 }         


                    {           "zip"           : 56000 } -->> {           "zip"           : 56800 } on : shard0002 {           "t"           : 3,           "i"           : 4 }         


                    {           "zip"           : 56800 } -->> {           "zip"           : 57088 } on : shard0002 {           "t"           : 4,           "i"           : 2 }         


                    {           "zip"           : 57088 } -->> {           "zip"           : 57500 } on : shard0002 {           "t"           : 4,           "i"           : 3 }         


                    {           "zip"           : 57500 } -->> {           "zip"           : 58140 } on : shard0001 {           "t"           : 4,           "i"           : 0 }         


                    {           "zip"           : 58140 } -->> {           "zip"           : 59000 } on : shard0000 {           "t"           : 4,           "i"           : 1 }         


                    {           "zip"           : 59000 } -->> {           "zip"           : {           "$maxKey"           : 1 } } on : shard0000 {           "t"           : 3,           "i"           : 3 }         


                    {            "_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


use config         


          db.locks.          find          ( { _id :           "balancer"           } ).pretty()         


          {             "_id"           :           "balancer"          ,         


          "process"           :           "mongos0.example.net:1292810611:1804289383"          ,         


                    "state"           : 2,         


                    "ts"           : ObjectId(          "4d0f872630c42d1978be8a2e"          ),         


                    "when"           :           "Mon Dec 20 2010 11:41:10 GMT-0500 (EST)"          ,         


                    "who"           :           "mongos0.example.net:1292810611:1804289383:Balancer:846930886"          ,         


                    "why"           :           "doing balance round"           }



state=2 表示正在进行balance, 在2.0版本之前这个值是1


配置balance时间窗口

可以通过balance时间窗口指定在一天之内的某段时间之内可以进行balance, 其他时间不得进行balance。

先连接到任意一台mongos


use config         


          db.settings.update({ _id :           "balancer"           }, { $          set           : { activeWindow : { start :           "23:00"          , stop :           "6:00"           } } },           true           )



这个设置让只有从23:00到6:00之间可以进行balance。


也可以取消时间窗口设置:

use config         


          db.settings.update({ _id :           "balancer"           }, { $          unset           : { activeWindow :           true           } })





修改chunk size

这是一个全局的参数。 默认是64MB。

小的chunk会让不同的shard数据量更均衡。 但会导致更多的Migration。

大的chunk会减少migration。不同的shard数据量不均衡。

这样修改chunk size。先连接上任意mongos


db.settings.save( { _id:          "chunksize"          , value: <size> } )


单位是MB



何时会自动balance

每个mongos进程都可能发动balance。

一次只会有一个balance跑。 这是因为需要竞争这个锁:

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


上面的treshhold从2.2版本开始生效。


一旦balancer开始行动起来,只有当任意两个shard的chunk数量小于2或者是migration失败才会停止。



设置分片上最大的存储容量

有两种方式,第一种在添加分片时候用maxSize参数指定:

db.runCommand( { addshard : "example.net:34008", maxSize : 125 } )

第二种方式可以在运行中修改设定:



use config         


          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时间窗之外

手动迁移的方法:


chunks:         


                    shard0000       2         


                    shard0001       2         


                    {           "zipcode"           : {           "$minKey"           : 1 } } -->> {           "zipcode"           : 10001 } on : shard0000 Timestamp(6, 0)          


                    {           "zipcode"           : 10001 } -->> {           "zipcode"           : 23772 } on : shard0001 Timestamp(6, 1)          


                    {           "zipcode"           : 23772 } -->> {           "zipcode"           : 588377 } on : shard0001 Timestamp(3, 2)          


                    {           "zipcode"           : 588377 } -->> {           "zipcode"           : {           "$maxKey"           : 1 } } on : shard0000 Timestamp(5, 1)         


          mongos> db.adminCommand({moveChunk:           "contact.people"          ,           find          :{zipcode:10003}, to:          "192.168.1.135:20002"          })         


          {           "millis"           : 2207,           "ok"           : 1 }         


          mongos> sh.status()         


          --- Sharding Status ---          


                    sharding version: {         


                    "_id"           : 1,         


                    "version"           : 3,         


                    "minCompatibleVersion"           : 3,         


                    "currentVersion"           : 4,         


                    "clusterId"           : ObjectId(          "52ece49ae6ab22400d937891"          )         


          }         


                    shards:         


                    {            "_id"           :           "shard0000"          ,            "host"           :           "192.168.1.135:20002"           }         


                    {            "_id"           :           "shard0001"          ,            "host"           :           "192.168.1.135:20003"           }         


                    databases:         


                    {            "_id"           :           "admin"          ,            "partitioned"           :           false          ,            "primary"           :           "config"           }         


                    {            "_id"           :           "test"          ,            "partitioned"           :           false          ,            "primary"           :           "shard0000"           }         


                    {            "_id"           :           "contact"          ,            "partitioned"           :           true          ,            "primary"           :           "shard0000"           }         


                    contact.people         


                    shard key: {           "zipcode"           : 1 }         


                    chunks:         


                    shard0000       3         


                    shard0001       1         


                    {           "zipcode"           : {           "$minKey"           : 1 } } -->> {           "zipcode"           : 10001 } on : shard0000 Timestamp(6, 0)          


                    {           "zipcode"           : 10001 } -->> {           "zipcode"           : 23772 } on : shard0000 Timestamp(7, 0)          


                    {           "zipcode"           : 23772 } -->> {           "zipcode"           : 588377 } on : shard0001 Timestamp(7, 1)          


                    {           "zipcode"           : 588377 } -->> {           "zipcode"           : {           "$maxKey"           : 1 } } on : shard0000 Timestamp(5, 1)          


                    


          mongos>





预分配空chunk

这是一种提高写效率的方法。相当于在写入真实数据之前,就分配好了数据桶,然后再对号入座。省去了创建chunk和split的时间。

实际上使用的是split命令:

db.runCommand( { split : "myapp.users" , middle : { email : prefix } } );

myapp.users 是 collection的名字。


middle参数是split的点。

split命令如下:

db.adminCommand( { split: <database>.<collection>, <find|middle|bounds> } )

find 表示查找到的记录进行分裂


bounds是指定[low, up]分裂

middle是指定分裂的点。

一个预分配chunk的例子如下:


for           (           var           x=97; x<97+26; x++ ){         


                    for          (           var           y=97; y<97+26; y+=6 ) {         


                    var           prefix = String.fromCharCode(x) + String.fromCharCode(y);         


                    db.runCommand( { split :           "myapp.users"           , middle : { email : prefix } } );         


                    }         


          }



这个预分配的目的是字母顺序有一定间隔的email, 分配到不同的chunk里。

例如aa-ag到一个chunk

ag-am到一个chunk

预分配的结果如下:

{           "email"           : {           "$minKey"           : 1 } } -->> {           "email"           :           "aa"           } on : shard0001 Timestamp(2, 0)          


          {           "email"           :           "aa"           } -->> {           "email"           :           "ag"           } on : shard0001 Timestamp(3, 0)          


          {           "email"           :           "ag"           } -->> {           "email"           :           "am"           } on : shard0001 Timestamp(4, 0)          


          {           "email"           :           "am"           } -->> {           "email"           :           "as"           } on : shard0001 Timestamp(5, 0)          


          {           "email"           :           "as"           } -->> {           "email"           :           "ay"           } on : shard0001 Timestamp(6, 0)         


          ...


{           "email"           :           "zm"           } -->> {           "email"           :           "zs"           } on : shard0000 Timestamp(1, 257)          


          {           "email"           :           "zs"           } -->> {           "email"           :           "zy"           } on : shard0000 Timestamp(1, 259)          


          {           "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上运行db.shutdownServer()关闭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()



新添加的分片始终不进行数据同步的问题



1 如果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})



2 另外一个新的分片始终部进行更新的问题:

日志里出现这样的错误:

secondaryThrottle on, but doc insert timed out after 60 seconds, continuing

通过1 将所有分片的secondary和arbitary删除掉,2 重启同步的分片解决。

找到这个问题的解决方法是看到mongo/s/d_migration.cpp里有这样一段代码


if           ( secondaryThrottle && thisTime > 0 ) {         


                    if           ( ! waitForReplication( cc().getLastOp(), 2, 60           /* seconds to wait */           ) ) {         


                    warning() <<           "secondaryThrottle on, but doc insert timed out after 60 seconds, continuing"           << endl;         


                    }         


          }

这段代码含义是,要进行同步的chunk所在的分片的从服务的secondary的optime和主分片不一致,所以需要等待60秒钟的时间。


所以将要进行同步的chunk所在分片的复制集secondary和arbiter都删除掉,再重启新分片的mongod之后解决。



3 mongod的日志出现:



moveChunk cannot start migration with zero version

解决方法,在mongos上运行


mongos> use admin
 switched to db admin
 mongos> db.runCommand("flushRouterConfig");{ "flushed" : true, "ok" : 1 }