MongoDB水平分片集群學習筆記

jopen 10年前發布 | 50K 次閱讀 MongoDB NoSQL數據庫

為何需要水平分片

1 減少單機請求數,將單機負載,提高總負載

2 減少單機的存儲空間,提高總存空間。

下圖一目了然:

MongoDB水平分片集群學習筆記


mongodb sharding 服務器架構

MongoDB水平分片集群學習筆記

簡單注解:

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

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})

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)

來自:http://my.oschina.net/costaxu/blog/196980

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!