MongoDB 分片的原理、搭建、應用 (轉)


一、概念:

      分片(sharding)是指將數據庫拆分,將其分散在不同的機器上的過程。將數據分散到不同的機器上,不需要功能強大的服務器就可以存儲更多的數據和處理更大的負載。基本思想就是將集合切成小塊,這些塊分散到若干片里,每個片只負責總數據的一部分,最后通過一個均衡器來對各個分片進行均衡(數據遷移)。通過一個名為mongos的路由進程進行操作,mongos知道數據和片的對應關系(通過配置服務器)。大部分使用場景都是解決磁盤空間的問題,對於寫入有可能會變差(+++里面的說明+++),查詢則盡量避免跨分片查詢。使用分片的時機:

1,機器的磁盤不夠用了。使用分片解決磁盤空間的問題。 2,單個mongod已經不能滿足寫數據的性能要求。通過分片讓寫壓力分散到各個分片上面,使用分片服務器自身的資源。 3,想把大量數據放到內存里提高性能。和上面一樣,通過分片使用分片服務器自身的資源。

二、部署安裝: 前提是安裝了mongodb(本文用3.0測試)

在搭建分片之前,先了解下分片中各個角色的作用。

① 配置服務器。是一個獨立的mongod進程,保存集群和分片的元數據,即各分片包含了哪些數據的信息。最先開始建立,啟用日志功能。像啟動普通的mongod一樣啟動配置服務器,指定configsvr選項。不需要太多的空間和資源,配置服務器的1KB空間相當於真是數據的200MB。保存的只是數據的分布表。當服務不可用,則變成只讀,無法分塊、遷移數據。
② 路由服務器。即mongos,起到一個路由的功能,供程序連接。本身不保存數據,在啟動時從配置服務器加載集群信息,開啟mongos進程需要知道配置服務器的地址,指定configdb選項。
③ 分片服務器。是一個獨立普通的mongod進程,保存數據信息。可以是一個副本集也可以是單獨的一台服務器。

部署環境:3台機子

A:配置(3)、路由1、分片1;

B:分片2,路由2;

C:分片3

      在部署之前先明白片鍵的意義,一個好的片鍵對分片至關重要。片鍵必須是一個索引,數據根據這個片鍵進行拆分分散。通過sh.shardCollection加會自動創建索引。一個自增的片鍵對寫入和數據均勻分布就不是很好,因為自增的片鍵總會在一個分片上寫入,后續達到某個閥值可能會寫到別的分片。但是按照片鍵查詢會非常高效。隨機片鍵對數據的均勻分布效果很好。注意盡量避免在多個分片上進行查詢。在所有分片上查詢,mongos會對結果進行歸並排序。

啟動上面這些服務,因為在后台運行,所以用配置文件啟動,配置文件說明

1)配置服務器的啟動。(A上開啟3個,Port:20000、21000、22000) 

配置服務器是一個普通的mongod進程,所以只需要新開一個實例即可。配置服務器必須開啟1個或則3個,開啟2個則會報錯

BadValue need either 1 or 3 configdbs

因為要放到后台用用配置文件啟動,需要修改配置文件:

/etc/mongod_20000.conf

復制代碼
#數據目錄
dbpath=/usr/local/config/ #日志文件 logpath=/var/log/mongodb/mongodb_config.log #日志追加 logappend=true #端口 port = 20000 #最大連接數 maxConns = 50 pidfilepath = /var/run/mongo_20000.pid #日志,redo log journal = true #刷寫提交機制 journalCommitInterval = 200 #守護進程模式 fork = true #刷寫數據到日志的頻率 syncdelay = 60 #storageEngine = wiredTiger #操作日志,單位M oplogSize = 1000 #命名空間的文件大小,默認16M,最大2G。 nssize = 16 noauth = true unixSocketPrefix = /tmp configsvr = true
復制代碼

/etc/mongod_21000.conf

復制代碼
數據目錄
dbpath=/usr/local/config1/ #日志文件 logpath=/var/log/mongodb/mongodb_config1.log #日志追加 logappend=true #端口 port = 21000 #最大連接數 maxConns = 50 pidfilepath = /var/run/mongo_21000.pid #日志,redo log journal = true #刷寫提交機制 journalCommitInterval = 200 #守護進程模式 fork = true #刷寫數據到日志的頻率 syncdelay = 60 #storageEngine = wiredTiger #操作日志,單位M oplogSize = 1000 #命名空間的文件大小,默認16M,最大2G。 nssize = 16 noauth = true unixSocketPrefix = /tmp configsvr = true
復制代碼

開啟配置服務器:

復制代碼
root@mongo1:~# mongod -f /etc/mongod_20000.conf 
about to fork child process, waiting until server is ready for connections. forked process: 8545 child process started successfully, parent exiting root@mongo1:~# mongod -f /etc/mongod_21000.conf about to fork child process, waiting until server is ready for connections. forked process: 8595 child process started successfully, parent exiting
復制代碼

同理再起一個22000端口的配置服務器。

View Code

2)路由服務器的啟動。(A、B上各開啟1個,Port:30000)

路由服務器不保存數據,把日志記錄一下即可。

復制代碼
# mongos

#日志文件
logpath=/var/log/mongodb/mongodb_route.log #日志追加 logappend=true #端口 port = 30000 #最大連接數 maxConns = 100 #綁定地址 #bind_ip=192.168.200.*,..., pidfilepath = /var/run/mongo_30000.pid configdb=192.168.200.A:20000,192.168.200.A:21000,192.168.200.A:22000 #必須是1個或則3個配置 。
#configdb=127.0.0.1:20000 #報錯
#守護進程模式 fork = true
復制代碼

其中最重要的參數是configdb,不能在其后面帶的配置服務器的地址寫成localhost或則127.0.0.1,需要設置成其他分片也能訪問的地址,即192.168.200.A:20000/21000/22000。否則在addshard的時候會報錯:

{
"ok" : 0, "errmsg" : "can't use localhost as a shard since all shards need to communicate. either use all shards and configdbs in localhost or all in actual IPs host: 172.16.5.104:20000 isLocalHost:0" }

開啟mongos:

root@mongo1:~# mongos -f /etc/mongod_30000.conf 
2015-07-10T14:42:58.741+0800 W SHARDING running with 1 config server should be done only for testing purposes and is not recommended for production about to fork child process, waiting until server is ready for connections. forked process: 8965 child process started successfully, parent exiting

3)分片服務器的啟動:

就是一個普通的mongod進程:

root@mongo1:~# mongod -f /etc/mongod_40000.conf 
note: noprealloc may hurt performance in many applications about to fork child process, waiting until server is ready for connections. forked process: 9020 child process started successfully, parent exiting

A服務器上面的服務開啟完畢

root@mongo1:~# ps -ef | grep mongo root 9020 1 0 14:47 ? 00:00:06 mongod -f /etc/mongod_40000.conf root 9990 1 0 15:14 ? 00:00:02 mongod -f /etc/mongod_20000.conf root 10004 1 0 15:14 ? 00:00:01 mongod -f /etc/mongod_21000.conf root 10076 1 0 15:20 ? 00:00:00 mongod -f /etc/mongod_22000.conf root 10096 1 0 15:20 ? 00:00:00 mongos -f /etc/mongod_30000.conf

按照上面的方法再到B上開啟分片服務和路由服務(配置文件一樣),以及在C上開啟分片服務。到此分片的配置服務器、路由服務器、分片服務器都已經部署完成。

三、配置分片:下面的操作都是在mongodb的命令行里執行

1)添加分片sh.addShard("IP:Port") 

登陸路由服務器mongos 操作:

root@mongo1:~# mongo --port=30000 MongoDB shell version: 3.0.4 connecting to: 127.0.0.1:30000/test mongos> 

添加分片:

復制代碼
mongos> sh.status() #查看集群的信息 --- Sharding Status --- 
  sharding version: {
    "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("559f72470f93270ba60b26c6") } shards: 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" : "admin", "partitioned" : false, "primary" : "config" } mongos> sh.addShard("192.168.200.A:40000") #添加分片 { "shardAdded" : "shard0000", "ok" : 1 } mongos> sh.addShard("192.168.200.B:40000") #添加分片 { "shardAdded" : "shard0001", "ok" : 1 } mongos> sh.addShard("192.168.200.C:40000") #添加分片 { "shardAdded" : "shard0002", "ok" : 1 } mongos> sh.status() #查看集群信息 --- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("559f72470f93270ba60b26c6") } shards: #分片信息 { "_id" : "shard0000", "host" : "192.168.200.A:40000" } { "_id" : "shard0001", "host" : "192.168.200.B:40000" } { "_id" : "shard0002", "host" : "192.168.200.C:40000" } 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" : "admin", "partitioned" : false, "primary" : "config" }
復制代碼

2)開啟分片功能:sh.enableSharding("庫名")、sh.shardCollection("庫名.集合名",{"key":1})

復制代碼
mongos> sh.enableSharding("dba") #首先對數據庫啟用分片
{ "ok" : 1 } mongos> sh.status() #查看分片信息 --- Sharding Status ---...
... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } mongos> sh.shardCollection("dba.account",{"name":1}) #再對集合進行分片,name字段是片鍵。片鍵的選擇:利於分塊、分散寫請求、查詢數據。 { "collectionsharded" : "dba.account", "ok" : 1 } mongos> sh.status() --- Sharding Status ---... shards: { "_id" : "shard0000", "host" : "192.168.200.51:40000" } { "_id" : "shard0001", "host" : "192.168.200.52:40000" } { "_id" : "shard0002", "host" : "192.168.200.53:40000" } ... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } #庫 dba.account shard key: { "name" : 1 } #集合 chunks: shard0000 1 { "name" : { "$minKey" : 1 } } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(1, 0)
復制代碼

上面加粗部分表示分片信息已經配置完成。要是出現:

too many chunks to print, use verbose if you want to force print

想要看到詳細的信息則需要執行:

mongos> sh.status({"verbose":1}) 或則 mongos> db.printShardingStatus("vvvv") 或則 mongos> printShardingStatus(db.getSisterDB("config"),1)

四、測試 :對dba庫的account集合進行測試,隨機寫入,查看是否分散到3個分片中。

判斷是否為shard:db.runCommand({isdbgrid:1})

mongos> db.runCommand({isdbgrid:1}) { "isdbgrid" : 1, "hostname" : "mongo3c", "ok" : 1 }

通過一個python腳本進行隨機寫入:分別向A、B 2個mongos各寫入10萬條記錄。

復制代碼
#!/usr/bin/env python #-*- coding:utf-8 -*- #隨即寫MongoDB Shard 測試 import pymongo import time from random import Random def random_str(randomlength=8): str = '' chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' length = len(chars) - 1 random = Random() for i in range(randomlength): str+=chars[random.randint(0, length)] return str def inc_data(conn): db = conn.dba # db = conn.test collection = db.account for i in range(100000): str = '' chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' length = len(chars) - 1 random = Random() for i in range(15): str+=chars[random.randint(0, length)] string = str collection.insert({"name" : string, "age" : 123+i, "address" : "hangzhou"+string}) if __name__ =='__main__': conn = pymongo.MongoClient(host='192.168.200.A/B',port=30000) StartTime = time.time() print "===============$inc===============" print "StartTime : %s" %StartTime inc_data(conn) EndTime = time.time() print "EndTime : %s" %EndTime CostTime = round(EndTime-StartTime) print "CostTime : %s" %CostTime
復制代碼

查看是否分片:db.collection.stats()

復制代碼
mongos> db.account.stats() #查看集合的分布情況
...
... "shards" : { "shard0000" : { "ns" : "dba.account", "count" : 89710, "size" : 10047520, ...
... "shard0001" : { "ns" : "dba.account", "count" : 19273, "size" : 2158576, ...
... "shard0002" : { "ns" : "dba.account", "count" : 91017, "size" : 10193904, ...
...
復制代碼

上面加粗部分為集合的基本信息,可以看到分片成功,各個分片都有數據(count)。到此MongoDB分片集群搭建成功。

++++++++++++++++++++++++++++++++++++++++++++++++

感興趣的同學可以看下面這個比較有趣的現象:

復制代碼
#在寫之前分片的基本信息:
mongos> sh.status() --- Sharding Status --- ... ... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } dba.account shard key: { "name" : 1 } chunks: shard0000 1 { "name" : { "$minKey" : 1 } } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(1, 0) #可以看到這里片鍵的寫入,都是寫在shard0000里面的。  #在寫期間的分片基本信息: mongos> sh.status() --- Sharding Status --- ... ... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } dba.account shard key: { "name" : 1 }  chunks: #數據塊分布 shard0000 1 shard0001 1 shard0002 1 { "name" : { "$minKey" : 1 } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(2, 0) { "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(3, 0) { "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(3, 1) #可以看到片鍵寫入的基本分布  #在寫完成后的基本信息: mongos> sh.status() --- Sharding Status --- ... ... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } dba.account shard key: { "name" : 1 } chunks: #數據塊分布  shard0000 2 shard0001 1 shard0002 2 { "name" : { "$minKey" : 1 } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(2, 0) { "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : shard0000 Timestamp(4, 0) { "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(4, 1) { "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(3, 4) { "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(3, 1) #最后片鍵寫入的分布
復制代碼

上面加粗的信息對比上看到,本來在每個分片上都只有一個塊,最后在shard0000、shard0002上有2個塊,被拆分了。shard0001不變。這是因為mongos在收到寫請求的時候,會檢查當前塊的拆分閥值點。到達該閥值的時候,會向分片發起一個拆分的請求。例子中shard0000和shard0002里的塊被拆分了。分片內的數據進行了遷移(有一定的消耗),最后通過一個均衡器來對數據進行轉移分配。所以在寫入途中要是看到一個分片中集合的數量變小也是正常的。

balancer:  #均衡器
    Currently enabled:  yes
    Currently running:  yes #正在轉移
        Balancer lock taken at Fri Jul 10 2015 22:57:27 GMT+0800 (CST) by mongo2:30000:1436540125:1804289383:Balancer:846930886

均衡器:均衡器負責數據遷移,周期性的檢查分片是否存在不均衡,如果不存在則會開始塊的遷移,config.locks集合里的state表示均衡器是否找正在運行,0表示非活動狀態,2表示正在均衡。均衡遷移數據的過程會增加系統的負載:目標分片必須查詢源分片的所有文檔,將文檔插入目標分片中,再清除源分片的數據。可以關閉均衡器(不建議):關閉會導致各分片數據分布不均衡,磁盤空間得不到有效的利用。 

mongos> sh.setBalancerState(false)  #關閉自動均衡器,手動均衡,打開:sh.setBalancerState(true)
mongos> db.settings.find() #查看均衡器狀態 { "_id" : "balancer", "stopped" : true }

可以為均衡器設置一個均衡時間窗口:activeWindow

mongos> db.settings.update({"_id":"balancer"},{"$set":{"activeWindow":{"start":"08:00","stop":"02:00"}}},true) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) mongos> db.settings.find({"_id":"balancer"}) { "_id" : "balancer", "stopped" : false, "activeWindow" : { "start" : "08:00", "stop" : "02:00" } }

上面說明:均衡只會在早上8點到凌晨2點進行均衡操作。均衡器是以塊的數量作為遷移指標,而非數據大小,塊的大小默認是64M,可以修改:(config.settings)

mongos> db.settings.find() { "_id" : "chunksize", "value" : 64 } mongos> db.settings.save({"_id":"chunksize","value":32}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) mongos> db.settings.find() { "_id" : "chunksize", "value" : 32 }

上面把塊的默認大小改成了32M,除了通過均衡器自動遷移外,還可以手動遷移數據sh.moveChunk("db.collection",{塊地址},"新片名稱")

復制代碼
mongos> db.chunks.find({"_id" : "abc.account-name_\"wPeFnJEvendSTbH\""}).pretty() #先到config.chunks上任意找一個塊 { "_id" : "abc.account-name_\"wPeFnJEvendSTbH\"", "lastmod" : Timestamp(3, 1), "lastmodEpoch" : ObjectId("55a52ff1fdd9a605a0371327"), "ns" : "abc.account", "min" : { "name" : "wPeFnJEvendSTbH" #被移動的塊 }, "max" : { "name" : { "$maxKey" : 1 } }, "shard" : "shard0000" #原先所在的分片 } mongos> sh.moveChunk("abc.account",{"name" : "wPeFnJEvendSTbH"},"mablevi") #把abc.account集合中包含name(片鍵)為""的快遷移到mablevi分片中 { "millis" : 6800, "ok" : 1 } mongos> db.chunks.find({"_id" : "abc.account-name_\"wPeFnJEvendSTbH\""}).pretty() { "_id" : "abc.account-name_\"wPeFnJEvendSTbH\"", "lastmod" : Timestamp(5, 0), "lastmodEpoch" : ObjectId("55a52ff1fdd9a605a0371327"), "ns" : "abc.account", "min" : { "name" : "wPeFnJEvendSTbH" }, "max" : { "name" : { "$maxKey" : 1 } }, "shard" : "mablevi" #已被遷移到新片 }
復制代碼

上面是手動移動數據的操作,數據被移動。 要是塊超出了64M限制【原因是片鍵沒選好(日期、狀態值等),導致一個塊無限增大】,則無法進行自動均衡,無法分塊。有2個辦法:1是加大塊的大小(setting),2是拆分sh.splitAt()(推薦)。

所以要是遇到分片寫入比單點寫入慢就是因為分片路由服務(mongos)需要維護元數據、數據遷移、路由開銷等

++++++++++++++++++++++++++++++++++++++++++++++++

五、高可用:Sharding+Replset

上面的分片都是單點的,要是一個分片壞了,則數據會丟失,利用之前減少的副本集,能否把副本集加入到分片中?下面就來說明下。

1)添加副本集分片服務器(mmm副本集名稱):這里測試就只對一個分片加副本集,要實現完全的高可用就需要對所有分片加副本集,避免單點故障

一個普通的副本集:

View Code

現在需要把這個副本集加入到分片中:

復制代碼
mongos> sh.addShard("mmm/192.168.200.25:27017,192.168.200.245:27017,192.168.200.245:37017") #加入副本集分片
{ "shardAdded" : "mmm", "ok" : 1 } mongos> sh.status() --- Sharding Status --- ...
...
shards: { "_id" : "mmm", "host" : "mmm/192.168.200.245:27017,192.168.200.245:37017,192.168.200.25:27017" } { "_id" : "shard0000", "host" : "192.168.200.51:40000" } { "_id" : "shard0001", "host" : "192.168.200.52:40000" } { "_id" : "shard0002", "host" : "192.168.200.53:40000" } balancer: Currently enabled: yes Currently running: no Failed balancer rounds in last 5 attempts: 0 Migration Results for the last 24 hours: 4 : Success databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } dba.account shard key: { "name" : 1 } chunks: mmm 1 shard0000 1 shard0001 1 shard0002 2 { "name" : { "$minKey" : 1 } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(2, 0) { "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : mmm Timestamp(5, 0) { "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(4, 1) { "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(3, 4) { "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(5, 1) { "_id" : "abc", "partitioned" : false, "primary" : "shard0000" } #未設置分片
復制代碼

上面加粗部分表示副本集分片已經成功加入,並且新加入的分片會分到已有的分片數據

復制代碼
mongos> db.account.stats()
...
...
    "shards" : { "mmm" : { "ns" : "dba.account", "count" : 7723, #后加入的分片得到了數據 "size" : 741408, "avgObjSize" : 96, "storageSize" : 2793472, "numExtents" : 5, "nindexes" : 2, "lastExtentSize" : 2097152, "paddingFactor" : 1, "systemFlags" : 1, "userFlags" : 0, "totalIndexSize" : 719488, "indexSizes" : { "_id_" : 343392, "name_1" : 376096 }, "ok" : 1 }, ... ...
復制代碼

2)繼續用python腳本寫數據,填充到副本集中 

由於之前的副本集是比較老的版本(2.4),所以在寫入副本集分片的時候報錯:

復制代碼
mongos> db.account.insert({"name":"UavMbMlfsz1OFrz"}) WriteResult({ "nInserted" : 0, "writeError" : { "code" : 83, "errmsg" : "write results unavailable from 192.168.200.25:27017 :: caused by :: Location28563 cannot send batch write operation to server 192.168.200.25:27017 (192.168.200.25)" } })
復制代碼

太混蛋了,錯誤提示不太人性化,搞了半天。所以說版本一致性還是很重要的。現在重新開了一個副本集

View Code

把之前的副本集分片刪除了,如何刪除見下面3)。

新的副本集加入分片中:

復制代碼
mongos> sh.addShard("mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002")
{ "shardAdded" : "mablevi", "ok" : 1 } mongos> sh.status() --- Sharding Status --- ... ... shards: { "_id" : "mablevi", "host" : "mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002" } { "_id" : "shard0000", "host" : "192.168.200.51:40000" } { "_id" : "shard0001", "host" : "192.168.200.52:40000" } { "_id" : "shard0002", "host" : "192.168.200.53:40000" } ... ... dba.account shard key: { "name" : 1 } chunks: mablevi 1 shard0000 1 shard0001 1 shard0002 2 { "name" : { "$minKey" : 1 } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(2, 0)  { "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : mablevi Timestamp(9, 0) #新加入的分片得到數據 { "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(4, 1) { "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(3, 4) { "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(9, 1) { "_id" : "abc", "partitioned" : false, "primary" : "shard0000" } { "_id" : "mablevi", "partitioned" : false, "primary" : "shard0001" }
復制代碼

繼續用python寫入操作:

復制代碼
mongos> db.account.stats()
{
...
... "shards" : { "mablevi" : { "ns" : "dba.account", "count" : 47240, "size" : 5290880, ...
...
復制代碼

副本集的分片被寫入了47240條記錄。此時把副本集分片的Primary shutdown掉,再查看:

復制代碼
mongos> db.account.stats()
{
    "sharded" : true, "code" : 13639, "ok" : 0, "errmsg" : "exception: can't connect to new replica set master [192.168.200.53:50000], err: couldn't connect to server 192.168.200.53:50000 (192.168.200.53), connection attempt failed"  #由於副本集的Primary被shutdown之后,選舉新主還是要幾秒的時間,期間數據不能訪問,導致分片數據也不能訪問 } mongos> db.account.stats() ... ... "shards" : { "mablevi" : { "ns" : "dba.account", "count" : 47240,  #副本集新主選舉完畢之后,分片數據訪問正常。數據沒有丟失,高可用得到了實現。 "size" : 5290880, ... ...
復制代碼

要是讓副本集分片只剩下一台(Secondary),則分片會報錯: 

復制代碼
mongos> db.account.stats()
{
    "sharded" : true, "code" : 10009, "ok" : 0, "errmsg" : "exception: ReplicaSetMonitor no master found for set: mablevi" #數據不能訪問 }
復制代碼

3)刪除分片: db.runCommand({"removeshard":"mmm"})

要是覺得分片太多了,想刪除,則:

復制代碼
mongos> use admin #需要到admin下面刪除
switched to db admin
mongos> db.runCommand({"removeshard":"mmm"}) { "msg" : "draining started successfully", "state" : "started", #開始刪除,數據正在轉移 "shard" : "mmm", "ok" : 1 } mongos> sh.status() --- Sharding Status ---...
... shards: { "_id" : "mmm", "host" : "mmm/192.168.200.245:27017,192.168.200.245:37017,192.168.200.25:27017", "draining" : true } #刪除的分片數據移動到其他分片 { "_id" : "shard0000", "host" : "192.168.200.51:40000" } { "_id" : "shard0001", "host" : "192.168.200.52:40000" } { "_id" : "shard0002", "host" : "192.168.200.53:40000" } ...
... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } dba.account shard key: { "name" : 1 } chunks: shard0000 2 shard0001 1 shard0002 2 { "name" : { "$minKey" : 1 } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(2, 0) { "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : shard0000 Timestamp(8, 0) { "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(4, 1) #這里已經沒有了被刪除分片信息 { "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(3, 4) { "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(7, 1) { "_id" : "abc", "partitioned" : false, "primary" : "shard0000" } { "_id" : "mablevi", "partitioned" : false, "primary" : "shard0001" } mongos> db.runCommand({"removeshard":"mmm"}) #再次執行,直到執行成功,要是原來分片的數據比較大,這里比較費時,要是一個主分片則需要執行movePrimary { "msg" : "removeshard completed successfully", "state" : "completed", #完成刪除 "shard" : "mmm", "ok" : 1 } mongos> sh.status() --- Sharding Status ---... shards: #分片消失 { "_id" : "shard0000", "host" : "192.168.200.51:40000" } { "_id" : "shard0001", "host" : "192.168.200.52:40000" } { "_id" : "shard0002", "host" : "192.168.200.53:40000" } ...
... { "name" : { "$minKey" : 1 } } -->> { "name" : "5yyfY8mmR5HyhGJ" } on : shard0001 Timestamp(2, 0) { "name" : "5yyfY8mmR5HyhGJ" } -->> { "name" : "UavMbMlfszZOFrz" } on : shard0000 Timestamp(8, 0) { "name" : "UavMbMlfszZOFrz" } -->> { "name" : "t9LyVSNXDmf6esP" } on : shard0002 Timestamp(4, 1) #已經沒有了被刪除分片的信息 { "name" : "t9LyVSNXDmf6esP" } -->> { "name" : "woQAv99Pq1FVoMX" } on : shard0002 Timestamp(3, 4) { "name" : "woQAv99Pq1FVoMX" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(7, 1) { "_id" : "abc", "partitioned" : false, "primary" : "shard0000" } { "_id" : "mablevi", "partitioned" : false, "primary" : "shard0001" }
復制代碼

分片被刪除之后,數據被移到其他分片中,不會丟失。要是想讓主分片進行轉移則(movePrimary):

mongos> db.adminCommand({"movePrimary":"test","to":"shard0001"}) #把test的主分片從shard0000遷移到shard0001 

刷新下配置服務器:db.adminCommand({"flushRouterConfig":1})

db.adminCommand({"flushRouterConfig":1})

最后來查看下分片成員:db.runCommand({ listshards : 1 })

復制代碼
mongos> use admin  #需要進入admin才能執行
switched to db admin
mongos> db.runCommand({ listshards : 1 }) { "shards" : [ { "_id" : "shard0000", "host" : "192.168.200.51:40000" }, { "_id" : "shard0001", "host" : "192.168.200.52:40000" }, { "_id" : "shard0002", "host" : "192.168.200.53:40000" }, { "_id" : "mablevi", "host" : "mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002" } ], "ok" : 1 }
復制代碼

到此已經把MongoDB分片原理、搭建、應用大致已經介紹完。

六、認證分配

      上面的所有操作都是在無賬號密碼下進行的,這樣是不安全的,那如何使用賬號密碼呢?和副本級一樣,需要添加KeyFile參數,但是針對上面的三個角色(config、mongos、mongod)賬號密碼怎么添加呢?官網上已經做了說明:http://docs.mongodb.org/manual/tutorial/enable-authentication-in-sharded-cluster/。下面就對有賬號密碼認證分片進行相關設置說明

首先要創建賬號(Root角色)和生成一個KeyFile文件其中mongos 不需要創建賬號。

openssl rand -base64 741 > mongodb-keyfile chmod 600 mongodb-keyfile

其實這個文件也可以直接用明文,只要保證各個地方指定的文件是同一個就可以了。

1)mongd: 首先在mongod角色的分片成員上生成key file文件,特別注意的是有副本級的分片,再把這個文件分別復制到其他角色的服務器上。再添加參數:

auth = true keyFile = /usr/local/mongodb-keyfile

2)Config上添加參數:

auth = true keyFile = /usr/local/mongodb-keyfile

3)mongos上添加參數,因為mongos本來就是從config里加載數據的,所以只需要添加keyfile文件即可,不需要找上面createUser。

keyFile = /usr/local/mongodb-keyfile

最后重啟各個服務,再進入mongos里查看:

復制代碼
root@mongo1:/usr/local# mongo --port=30000 MongoDB shell version: 3.0.4 connecting to: 127.0.0.1:30000/test mongos> sh.status() #沒有認證,沒有權限報錯。 2015-07-14T23:42:11.800+0800 E QUERY Error: error: { "$err" : "not authorized for query on config.version", "code" : 13 } at Error (<anonymous>) at DBQuery.next (src/mongo/shell/query.js:259:15) at DBCollection.findOne (src/mongo/shell/collection.js:189:22) at printShardingStatus (src/mongo/shell/shardingtest.js:659:55) at Function.sh.status (src/mongo/shell/utils_sh.js:60:5) at (shell):1:4 at src/mongo/shell/query.js:259 mongos> use admin switched to db admin mongos> db.auth('dba','dba') #認證 1 mongos> sh.status() #有權限 --- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("55a51ef18bd517d4acec5ef9") } shards: { "_id" : "mablevi", "host" : "mablevi/192.168.200.53:50000,192.168.200.53:50001,192.168.200.53:50002" } { "_id" : "shard0000", "host" : "192.168.200.51:40000" } { "_id" : "shard0001", "host" : "192.168.200.52:40000" } { "_id" : "shard0002", "host" : "192.168.200.53:40000" } balancer:
...
... databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "test", "partitioned" : false, "primary" : "shard0000" } { "_id" : "dba", "partitioned" : true, "primary" : "shard0000" } dba.account shard key: { "name" : 1 } chunks: mablevi 1 shard0000 1 shard0001 2 shard0002 1 { "name" : { "$minKey" : 1 } } -->> { "name" : "9XXqCaBhfhPIXLq" } on : mablevi Timestamp(2, 0) { "name" : "9XXqCaBhfhPIXLq" } -->> { "name" : "RWINvgjYYQmbZds" } on : shard0002 Timestamp(4, 0) { "name" : "RWINvgjYYQmbZds" } -->> { "name" : "jSPRBNH8rvnzblG" } on : shard0001 Timestamp(4, 1) { "name" : "jSPRBNH8rvnzblG" } -->> { "name" : "okmjUUZuuKgftDC" } on : shard0001 Timestamp(3, 4) { "name" : "okmjUUZuuKgftDC" } -->> { "name" : { "$maxKey" : 1 } } on : shard0000 Timestamp(3, 1)
復制代碼

七、分片備份、還原

因為分片機制里面會有平衡器來遷移數據,所以各個分片里的數據很可能會移動,所以在備份分片時需要做:

①:先停止平衡器的工作,並檢查沒有chunk move動作,保證dump的時候沒有進行數據遷移。

mongos> sh.stopBalancer()

②:鎖定數據庫,保證數據沒有寫入:在各個分片上和配置服務器上執行。

> db.fsyncLock()
{
    "info" : "now locked against writes, use db.fsyncUnlock() to unlock", "seeAlso" : "http://dochub.mongodb.org/core/fsynccommand", "ok" : 1 }

③:執行備份操作,備份各個分片服務器和配置服務器。

mongodump -udba -p12345 -d dba_test --authenticationDatabase admin -o backup/

④:解鎖數據庫,備份完成之后在分片和配置服務器上解鎖數據庫,允許修改。

> db.fsyncUnlock()
{ "ok" : 1, "info" : "unlock completed" }

當數據庫出現問題,需要還原的時候,需要還原各個分片和配置服務器,並且重啟MongoDB實例。還原數據庫需要做:

①:還原各個分片和配置服務器。

mongorestore --host=127.0.0.1 --port=27017 -udba -p12345 -d dba_test --authenticationDatabase admin --drop backup/dba_test

②:重啟各個實例

總結:

      分片很好的解決了單台服務器磁盤空間、內存、cpu等硬件資源的限制問題,把數據水平拆分出去,降低單節點的訪問壓力。每個分片都是一個獨立的數據庫,所有的分片組合起來構成一個邏輯上的完整的數據庫。因此,分片機制降低了每個分片的數據操作量及需要存儲的數據量,達到多台服務器來應對不斷增加的負載和數據的效果。后面文章還會繼續對分片的其他方面進行說明介紹。

 

  https://www.cnblogs.com/zhoujinyi/p/4635444.html

 

參考文檔:

說明:http://docs.mongodb.org/manual/core/sharding-introduction/

配置:http://docs.mongodb.org/manual/tutorial/deploy-shard-cluster/

應用:http://www.caiyiting.com/blog/2014/replica-sets-sharding-realization.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM