按照上一節中《搭建高可用mongodb集羣(三)—— 深入副本集》搭建後還有兩個問題沒有解決:
- 從節點每個上面的數據都是對數據庫全量拷貝,從節點壓力會不會過大?
- 數據壓力大到機器支撐不了的時候能否做到自動擴展?
在系統早期,數據量還小的時候不會引起太大的問題,但是隨着數據量持續增多,後續遲早會出現一臺機器硬件瓶頸問題的。而mongodb主打的就是海量數據架構,他不能解決海量數據怎麼行!不行!“分片”就用這個來解決這個問題。
傳統數據庫怎麼做海量數據讀寫?其實一句話概括:分而治之。上圖看看就清楚了,如下 taobao嶽旭強在infoq中提到的 架構圖:
上圖中有個TDDL,是taobao的一個數據訪問層組件,他主要的作用是SQL解析、路由處理。根據應用的請求的功能解析當前訪問的sql判斷是在哪個業務數據庫、哪個表訪問查詢並返回數據結果。具體如圖:
說了這麼多傳統數據庫的架構,那Nosql怎麼去做到了這些呢?mysql要做到自動擴展需要加一個數據訪問層用程序去擴展,數據庫的增加、刪除、備份還需要程序去控制。一但數據庫的節點一多,要維護起來也是非常頭疼的。不過mongodb所有的這一切通過他自己的內部機制就可以搞定!頓時石化了,這麼牛X!還是上圖看看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.0.136、192.168.0.137、192.168.0.138。
- 2、分別在每臺機器上建立mongodb分片對應測試文件夾。
1 2 3 4 5 | #存放mongodb數據文件 mkdir -p /data/mongodbtest #進入mongodb文件夾 cd /data/mongodbtest |
3、下載mongodb的安裝程序包
1
2
3
4
|
wgethttp://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz
#解壓下載的壓縮包
tarxvzfmongodb-linux-x86_64-2.4.8.tgz
|
4、分別在每臺機器建立mongos 、config 、 shard1 、shard2、shard3 五個目錄。
因爲mongos不存儲數據,只需要建立日誌文件目錄即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #建立mongos目錄 mkdir -p /data/mongodbtest/mongos/log #建立config server 數據文件存放目錄 mkdir -p /data/mongodbtest/config/data #建立config server 日誌文件存放目錄 mkdir -p /data/mongodbtest/config/log #建立config server 日誌文件存放目錄 mkdir -p /data/mongodbtest/mongos/log #建立shard1 數據文件存放目錄 mkdir -p /data/mongodbtest/shard1/data #建立shard1 日誌文件存放目錄 mkdir -p /data/mongodbtest/shard1/log #建立shard2 數據文件存放目錄 mkdir -p /data/mongodbtest/shard2/data #建立shard2 日誌文件存放目錄 mkdir -p /data/mongodbtest/shard2/log #建立shard3 數據文件存放目錄 mkdir -p /data/mongodbtest/shard3/data #建立shard3 日誌文件存放目錄 mkdir -p /data/mongodbtest/shard3/log |
- 5、規劃5個組件對應的端口號,由於一個機器需要同時部署 mongos、config server 、shard1、shard2、shard3,所以需要用端口進行區分。
這個端口可以自由定義,在本文 mongos爲 20000, config server 爲 21000, shard1爲 22001 , shard2爲22002, shard3爲22003. - 6、在每一臺服務器分別啓動配置服務器。
1
|
/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod--configsvr--dbpath/data/mongodbtest/config/data--port21000--logpath/data/mongodbtest/config/log/config.log--fork
|
7、在每一臺服務器分別啓動mongos服務器。
1 | /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongos --configdb 192.168.0.136:21000,192.168.0.137:21000,192.168.0.138:21000 --port 20000 --logpath /data/mongodbtest/mongos/log/mongos.log --fork |
8、配置各個分片的副本集。
1
2
|
#在每個機器裏分別設置分片1服務器及副本集shard1
/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod--shardsvr--replSetshard1--port22001--dbpath/data/mongodbtest/shard1/data --logpath/data/mongodbtest/shard1/log/shard1.log--fork--nojournal --oplogSize10
|
爲了快速啓動並節約測試環境存儲空間,這裏加上 nojournal 是爲了關閉日誌信息,在我們的測試環境不需要初始化這麼大的redo日誌。同樣設置 oplogsize是爲了降低 local 文件的大小,oplog是一個固定長度的 capped collection,它存在於”local”數據庫中,用於記錄Replica Sets操作日誌。注意,這裏的設置是爲了測試!
1 2 | #在每個機器裏分別設置分片2服務器及副本集shard2 /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod --shardsvr --replSet shard2 --port 22002 --dbpath /data/mongodbtest/shard2/data --logpath /data/mongodbtest/shard2/log/shard2.log --fork --nojournal --oplogSize 10 |
1
2
|
#在每個機器裏分別設置分片3服務器及副本集shard3
/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod--shardsvr--replSetshard3--port22003--dbpath/data/mongodbtest/shard3/data --logpath/data/mongodbtest/shard3/log/shard3.log--fork--nojournal --oplogSize10
|
分別對每個分片配置副本集,深入瞭解副本集參考本系列前幾篇文章。
任意登陸一個機器,比如登陸192.168.0.136,連接mongodb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #設置第一個分片副本集 /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:22001 #使用admin數據庫 use admin #定義副本集配置 config = { _id:"shard1", members:[ {_id:0,host:"192.168.0.136:22001"}, {_id:1,host:"192.168.0.137:22001"}, {_id:2,host:"192.168.0.138:22001",arbiterOnly:true} ] } #初始化副本集配置 rs.initiate(config); #設置第二個分片副本集 /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:22002 #使用admin數據庫 use admin #定義副本集配置 config = { _id:"shard2", members:[ {_id:0,host:"192.168.0.136:22002"}, {_id:1,host:"192.168.0.137:22002"}, {_id:2,host:"192.168.0.138:22002",arbiterOnly:true} ] } #初始化副本集配置 rs.initiate(config); #設置第三個分片副本集 /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:22003 #使用admin數據庫 use admin #定義副本集配置 config = { _id:"shard3", members:[ {_id:0,host:"192.168.0.136:22003"}, {_id:1,host:"192.168.0.137:22003"}, {_id:2,host:"192.168.0.138:22003",arbiterOnly:true} ] } #初始化副本集配置 rs.initiate(config); |
9、目前搭建了mongodb配置服務器、路由服務器,各個分片服務器,不過應用程序連接到 mongos 路由服務器並不能使用分片機制,還需要在程序裏設置分片配置,讓分片生效。
1
2
3
4
5
6
7
8
|
#連接到mongos
/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:20000
#使用admin數據庫
user admin
#串聯路由服務器與分配副本集1
db.runCommand({addshard:"shard1/192.168.0.136:22001,192.168.0.137:22001,192.168.0.138:22001"});
|
如裏shard是單臺服務器,用 db.runCommand( { addshard : “[: ]” } )這樣的命令加入,如果shard是副本集,用db.runCommand( { addshard : “replicaSetName/[:port][,serverhostname2[:port],…]” });這樣的格式表示 。
1 2 | #串聯路由服務器與分配副本集2 db.runCommand( { addshard : "shard2/192.168.0.136:22002,192.168.0.137:22002,192.168.0.138:22002"}); |
1
2
|
#串聯路由服務器與分配副本集3
db.runCommand({addshard:"shard3/192.168.0.136:22003,192.168.0.137:22003,192.168.0.138:22003"});
|
1 2 | #查看分片服務器的配置 db.runCommand( { listshards : 1 } ); |
#內容輸出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
{
"shards":[
{
"_id":"shard1",
"host":"shard1/192.168.0.136:22001,192.168.0.137:22001"
},
{
"_id":"shard2",
"host":"shard2/192.168.0.136:22002,192.168.0.137:22002"
},
{
"_id":"shard3",
"host":"shard3/192.168.0.136:22003,192.168.0.137:22003"
}
],
"ok":1
}
|
- 因爲192.168.0.138是每個分片副本集的仲裁節點,所以在上面結果沒有列出來。
- 10、目前配置服務、路由服務、分片服務、副本集服務都已經串聯起來了,但我們的目的是希望插入數據,數據能夠自動分片,就差那麼一點點,一點點。。。連接在mongos上,準備讓指定的數據庫、指定的集合分片生效。
1 2 | #指定testdb分片生效 db.runCommand( { enablesharding :"testdb"}); |
1
2
|
#指定數據庫裏需要分片的集合和片鍵
db.runCommand({shardcollection:"testdb.table1",key:{id:1}})
|
- 我們設置testdb的 table1 表需要分片,根據 id 自動分片到 shard1 ,shard2,shard3 上面去。要這樣設置是因爲不是所有mongodb 的數據庫和表 都需要分片!
- 11、測試分片配置結果。
1 2 | #連接mongos服務器 /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:20000 |
1
2
|
#使用testdb
use testdb;
|
1 2 3 | #插入測試數據 for (var i = 1; i <= 100000; i++) db.table1.save({id:i,"test1":"testval1"}); |
1
2
|
#查看分片情況如下,部分無關信息省掉了
db.table1.stats();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | { "sharded" : true, "ns" : "testdb.table1", "count" : 100000, "numExtents" : 13, "size" : 5600000, "storageSize" : 22372352, "totalIndexSize" : 6213760, "indexSizes" : { "_id_" : 3335808, "id_1" : 2877952 }, "avgObjSize" : 56, "nindexes" : 2, "nchunks" : 3, "shards" : { "shard1" : { "ns" : "testdb.table1", "count" : 42183, "size" : 0, ... "ok" : 1 }, "shard2" : { "ns" : "testdb.table1", "count" : 38937, "size" : 2180472, ... "ok" : 1 }, "shard3" : { "ns" : "testdb.table1", "count" :18880, "size" : 3419528, ... "ok" : 1 } }, "ok" : 1 } |
- 可以看到數據分到3個分片,各自分片數量爲: shard1 “count” : 42183,shard2 “count” : 38937,shard3 “count” : 18880。已經成功了!不過分的好像不是很均勻,所以這個分片還是很有講究的,後續再深入討論。
- 12、java程序調用分片集羣,因爲我們配置了三個mongos作爲入口,就算其中哪個入口掛掉了都沒關係,使用集羣客戶端程序如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
publicclassTestMongoDBShards{
publicstaticvoidmain(String[]args){
try{
List<ServerAddress>addresses=newArrayList<ServerAddress>();
ServerAddressaddress1=newServerAddress("192.168.0.136",20000);
ServerAddressaddress2=newServerAddress("192.168.0.137",20000);
ServerAddressaddress3=newServerAddress("192.168.0.138",20000);
addresses.add(address1);
addresses.add(address2);
addresses.add(address3);
MongoClientclient=newMongoClient(addresses);
DBdb=client.getDB("testdb");
DBCollectioncoll=db.getCollection("table1");
BasicDBObjectobject=newBasicDBObject();
object.append("id",1);
DBObjectdbObject=coll.findOne(object);
System.out.println(dbObject);
}catch(Exceptione){
e.printStackTrace();
}
}
}
|
整個分片集羣搭建完了,思考一下我們這個架構是不是足夠好呢?其實還有很多地方需要優化,比如我們把所有的仲裁節點放在一臺機器,其餘兩臺機器承擔了全部讀寫操作,但是作爲仲裁的192.168.0.138相當空閒。讓機器3 192.168.0.138多分擔點責任吧!架構可以這樣調整,把機器的負載分的更加均衡一點,每個機器既可以作爲主節點、副本節點、仲裁節點,這樣壓力就會均衡很多了,如圖:
當然生產環境的數據遠遠大於當前的測試數據,大規模數據應用情況下我們不可能把全部的節點像這樣部署,硬件瓶頸是硬傷,只能擴展機器。要用好mongodb還有很多機制需要調整,不過通過這個東東我們可以快速實現高可用性、高擴展性,所以它還是一個非常不錯的Nosql組件。
再看看我們使用的mongodb java 驅動客戶端 MongoClient(addresses),這個可以傳入多個mongos 的地址作爲mongodb集羣的入口,並且可以實現自動故障轉移,但是負載均衡做的好不好呢?打開源代碼查看:
它的機制是選擇一個ping 最快的機器來作爲所有請求的入口,如果這臺機器掛掉會使用下一臺機器。那這樣。。。。肯定是不行的!萬一出現雙十一這樣的情況所有請求集中發送到這一臺機器,這臺機器很有可能掛掉。一但掛掉了,按照它的機制會轉移請求到下臺機器,但是這個壓力總量還是沒有減少啊!下一臺還是可能崩潰,所以這個架構還有漏洞!不過這個文章已經太長了,後續解決吧。