首先還是上圖看看mongodb通過哪些機制實現路由、分片:
從圖中可以看到有四個組件:mongos、config server、shard、replica set。
mongos,數據庫集羣請求的入口,所有的請求都通過mongos進行協調,不需要在應用程序添加一個路由選擇器,mongos自己就是一個請求分發中心,它負責把對應的數據請求請求轉發到對應的shard服務器上。在生產環境通常有多mongos作爲請求的入口,防止其中一個掛掉所有的mongodb請求都沒有辦法操作。
config server,顧名思義爲配置服務器,存儲所有數據庫元信息(路由、分片)的配置。mongos本身沒有物理存儲分片服務器和數據路由信息,只是緩存在內存裏,配置服務器則實際存儲這些數據。mongos第一次啓動或者關掉重啓就會從 config server 加載配置信息,以後如果配置服務器信息變化會通知到所有的 mongos 更新自己的狀態,這樣 mongos 就能繼續準確路由。在生產環境通常有多個 config server 配置服務器,因爲它存儲了分片路由的元數據,這個可不能丟失!就算掛掉其中一臺,只要還有存貨, mongodb集羣就不會掛掉。
shard,這就是傳說中的分片了。上面提到一個機器就算能力再大也有天花板,就像軍隊打仗一樣,一個人再厲害喝血瓶也拼不過對方的一個師。俗話說三個臭皮匠頂個諸葛亮,這個時候團隊的力量就凸顯出來了。在互聯網也是這樣,一臺普通的機器做不了的多臺機器來做,如下圖:
一臺機器的一個數據表 Collection1 存儲了 1T 數據,壓力太大了!在分給4個機器後,每個機器都是256G,則分攤了集中在一臺機器的壓力。也許有人問一臺機器硬盤加大一點不就可以了,爲什麼要分給四臺機器呢?不要光想到存儲空間,實際運行的數據庫還有硬盤的讀寫、網絡的IO、CPU和內存的瓶頸。在mongodb集羣只要設置好了分片規則,通過mongos操作數據庫就能自動把對應的數據操作請求轉發到對應的分片機器上。在生產環境中分片的片鍵可要好好設置,這個影響到了怎麼把數據均勻分到多個分片機器上,不要出現其中一臺機器分了1T,其他機器沒有分到的情況,這樣還不如不分片!
replica set,上兩節已經詳細講過了這個東東,怎麼這裏又來湊熱鬧!其實上圖4個分片如果沒有 replica set 是個不完整架構,假設其中的一個分片掛掉那四分之一的數據就丟失了,所以在高可用性的分片架構還需要對於每一個分片構建 replica set 副本集保證分片的可靠性。生產環境通常是 2個副本 + 1個仲裁。
說了這麼多,還是來實戰一下如何搭建高可用的mongodb集羣:
首先確定各個組件的數量,mongos 3個, config server 3個,數據分3片 shard server 3個,每個shard 有一個副本一個仲裁也就是 3 * 2 = 6 個,總共需要部署15個實例。這些實例可以部署在獨立機器也可以部署在一臺機器,我們這裏測試資源有限,只准備了 3臺機器,在同一臺機器只要端口不同就可以,看一下物理部署圖:
1、準備機器,IP分別設置爲: 192.168.5.100、192.168.5.101、192.168.5.102。
2、前期準備『3臺機器均需要操作』
#分別創建mongodbtest主目錄和存放mongodb的執行程序目錄 mkdir -p /data/mongodbtest /data/mongodbtest/bin #進到執行目錄解壓mongodb壓縮包並重新命名 cd /data/mongodbtest/bin tar -zxvf mongodb-linux-x86_64-2.6.1.tgz mv mongodb-linux-x86_64-2.6.1/ mongodb-2.6.1/ #把環境變量添加到/etc/profile文件中 export PATH=$PATH:/data/mongodbtest/bin/mongodb-2.6.1/bin/ #使配置生效 source /etc/profile
3、中期準備『3臺機器均需要操作』
#創建mongos日誌文件 mkdir -p /data/mongodbtest/mongos/log #建立config server 數據文件存放目錄和日誌目錄 mkdir -p /data/mongodbtest/config/data /data/mongodbtest/config/log #建立shard1 數據文件存放目錄和日誌目錄 mkdir -p /data/mongodbtest/shard1/data /data/mongodbtest/shard1/log #建立shard2 數據文件存放目錄和日誌目錄 mkdir -p /data/mongodbtest/shard2/data /data/mongodbtest/shard2/log #建立shard3 數據文件存放目錄和日誌目錄 mkdir -p /data/mongodbtest/shard3/data /data/mongodbtest/shard3/log
4、中期準備『規劃端口』
由於一個機器需要同時部署 mongos、config server 、shard1、shard2、shard3,所以需要用端口進行區分。 這個端口可以自由定義,在本文 mongos爲 20000, config server 爲 21000, shard1爲 22001 , shard2爲22002, shard3爲22003.
5、按順序啓動對應服務『服務啓動』
#分別啓動3臺機器的conf server mongod --configsvr --dbpath /data/mongodbtest/config/data --port 21000 --logpath /data/mongodbtest/config/log/config.log --fork #分別啓動3臺機器的mongos mongos --configdb 192.168.5.100:21000,192.168.5.101:21000,192.168.5.102:21000 --port 20000 --logpath /data/mongodbtest/mongos/log/mongos.log --fork #配置share1的副本集,爲了快速啓動並節約測試環境存儲空間,這裏加上 nojournal 是爲了關閉日誌信息,在我們的測試環境不需要初始化這麼大的redo日誌。同樣設置 oplogsize是爲了降低 local 文件的大小,oplog是一個固定長度的 capped collection,它存在於”local”數據庫中,用於記錄Replica Sets操作日誌。注意,這裏的設置是爲了測試 mongod --shardsvr --replSet shard1 --port 22001 --dbpath /data/mongodbtest/shard1/data --logpath /data/mongodbtest/shard1/log/shard1.log --fork --nojournal --oplogSize 10 #配置share2的副本集,爲了快速啓動並節約測試環境存儲空間,這裏加上 nojournal 是爲了關閉日誌信息,在我們的測試環境不需要初始化這麼大的redo日誌。同樣設置 oplogsize是爲了降低 local 文件的大小,oplog是一個固定長度的 capped collection,它存在於”local”數據庫中,用於記錄Replica Sets操作日誌。注意,這裏的設置是爲了測試 mongod --shardsvr --replSet shard2 --port 22002 --dbpath /data/mongodbtest/shard2/data --logpath /data/mongodbtest/shard2/log/shard2.log --fork --nojournal --oplogSize 10 #配置share3的副本集,爲了快速啓動並節約測試環境存儲空間,這裏加上 nojournal 是爲了關閉日誌信息,在我們的測試環境不需要初始化這麼大的redo日誌。同樣設置 oplogsize是爲了降低 local 文件的大小,oplog是一個固定長度的 capped collection,它存在於”local”數據庫中,用於記錄Replica Sets操作日誌。注意,這裏的設置是爲了測試 mongod --shardsvr --replSet shard3 --port 22003 --dbpath /data/mongodbtest/shard3/data --logpath /data/mongodbtest/shard3/log/shard3.log --fork --nojournal --oplogSize 10
6、配置每個shard副本集『配置副本集』
#登錄192.168.5.100的shard1 mongo實例設置share1 副本集 mongo 192.168.5.100:22001 use admin config = { _id:"shard1", members:[ {_id:0,host:"192.168.5.100:22001"}, {_id:1,host:"192.168.5.101:22001"}, {_id:2,host:"192.168.5.102:22001",arbiterOnly:true} ] } #初始化副本集配置 rs.initiate(config); #登錄192.168.5.100的shard2 mongo實例設置share2 副本集 mongo 192.168.5.100:22002 use admin config = { _id:"shard2", members:[ {_id:0,host:"192.168.5.100:22002"}, {_id:1,host:"192.168.5.101:22002"}, {_id:2,host:"192.168.5.102:22002",arbiterOnly:true} ] } #初始化副本集配置 rs.initiate(config); #登錄192.168.5.100的shard3 mongo實例設置share3 副本集 連接 mongo 192.168.5.100:22003 use admin config = { _id:"shard3", members:[ {_id:0,host:"192.168.5.100:22003"}, {_id:1,host:"192.168.5.101:22003"}, {_id:2,host:"192.168.5.102:22003",arbiterOnly:true} ] } #初始化副本集配置 rs.initiate(config);
7、目前搭建了mongodb配置服務器、路由服務器,各個分片服務器,不過應用程序連接到 mongos 路由服務器並不能使用分片機制,還需要在程序裏設置分片配置,讓分片生效。
#連接mongos mongo 127.0.0.1:20000 #注意 這裏要加上逗號 user admin; #串聯路由器與分片副本集1 db.runCommand( { addshard:"shard1/192.168.5.100:22001,192.168.5.101:22001,192.168.5.102:22001"}); #串聯路由器與分片副本集2 user admin; db.runCommand( { addshard:"shard2/192.168.5.100:22002,192.168.5.101:22002,192.168.5.102:22002"}); #串聯路由器與分片副本集3 user admin; db.runCommand( { addshard:"shard3/192.168.5.100:22003,192.168.5.101:22003,192.168.5.102:22003"}); #查看分片服務器的配置 db.runCommand( { listshards : 1 } ); #內容輸出 { "shards" : [ { "_id" : "shard1", "host" : "shard1/192.168.5.100:22001,192.168.5.101:22001" }, { "_id" : "shard2", "host" : "shard2/192.168.5.100:22002,192.168.5.101:22002" }, { "_id" : "shard3", "host" : "shard3/192.168.5.100:22003,192.168.5.101:22003" } ], "ok" : 1 }
因爲192.168.5.103是每個分片副本集的仲裁節點,所以在上面結果沒有列出來。
8、目前配置服務、路由服務、分片服務、副本集服務都已經串聯起來了,但我們的目的是希望插入數據,數據能夠自動分片,就差那麼一點點,一點點。。。
連接在mongos上,準備讓指定的數據庫、指定的集合分片生效。
#指定testdb分片生效 db.runCommand( { enablesharding :"testdb"}); #指定數據庫裏需要分片的集合和片鍵 db.runCommand( { shardcollection :"testdb.table1",key : {id: 1} } )
9、測試循環插入200000條數據,查看效果。
for (var i = 1; i <= 200000; i++) db.table1.save({id:i,"test1":"testval1"})
10、查看效果
mongos> db.printShardingStatus() db.printShardingStatus() --- Sharding Status --- sharding version: { "_id" : 1, "version" : 4, "minCompatibleVersion" : 4, "currentVersion" : 5, "clusterId" : ObjectId("5c79632ca881001461d466cb") } shards: { "_id" : "shard1", "host" : "shard1/192.168.5.100:22001,192.168.5.101:22001" } { "_id" : "shard2", "host" : "shard2/192.168.5.100:22002,192.168.5.101:22002" } { "_id" : "shard3", "host" : "shard3/192.168.5.100:22003,192.168.5.101:22003" } databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "testdb", "partitioned" : true, "primary" : "shard1" } testdb.table1 shard key: { "id" : 1 } chunks: shard3 5 shard1 5 shard2 6 #這裏就是前面文章說的chunk了 { "id" : { "$minKey" : 1 } } -->> { "id" : 1 } on : shard3 Timestamp(8, 0) { "id" : 1 } -->> { "id" : 4836 } on : shard3 Timestamp(11, 0) { "id" : 4836 } -->> { "id" : 79734 } on : shard1 Timestamp(11, 1) { "id" : 79734 } -->> { "id" : 155925 } on : shard3 Timestamp(5, 1) { "id" : 155925 } -->> { "id" : 267774 } on : shard3 Timestamp(4, 2) { "id" : 267774 } -->> { "id" : 435549 } on : shard1 Timestamp(6, 2) { "id" : 435549 } -->> { "id" : 603325 } on : shard1 Timestamp(6, 4) { "id" : 603325 } -->> { "id" : 687213 } on : shard3 Timestamp(12, 0) { "id" : 687213 } -->> { "id" : 771101 } on : shard2 Timestamp(12, 1) { "id" : 771101 } -->> { "id" : 943497 } on : shard2 Timestamp(8, 2) { "id" : 943497 } -->> { "id" : 1093292 } on : shard2 Timestamp(11, 4) { "id" : 1093292 } -->> { "id" : 1168190 } on : shard2 Timestamp(11, 6) { "id" : 1168190 } -->> { "id" : 1279045 } on : shard2 Timestamp(11, 7) { "id" : 1279045 } -->> { "id" : 1624908 } on : shard2 Timestamp(9, 2) { "id" : 1624908 } -->> { "id" : 1949761 } on : shard1 Timestamp(11, 2) { "id" : 1949761 } -->> { "id" : { "$maxKey" : 1 } } on : shard1 Timestamp(11, 3) { "_id" : "test", "partitioned" : false, "primary" : "shard1" } { "_id" : "dbtest", "partitioned" : false, "primary" : "shard1" } { "_id" : "noshare", "partitioned" : false, "primary" : "shard3" } { "_id" : "noshare2", "partitioned" : true, "primary" : "shard3" } noshare2.table2 shard key: { "id" : 1 } chunks: shard2 1 shard3 2 shard1 1 { "id" : { "$minKey" : 1 } } -->> { "id" : 100000 } on : shard2 Timestamp(3, 0) { "id" : 100000 } -->> { "id" : 137449 } on : shard3 Timestamp(3, 2) { "id" : 137449 } -->> { "id" : 200000 } on : shard3 Timestamp(3, 3) { "id" : 200000 } -->> { "id" : { "$maxKey" : 1 } } on : shard1 Timestamp(2, 0) { "_id" : "conf", "partitioned" : false, "primary" : "shard3" }