按照上一節中《搭建高可用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 |
wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz #解壓下載的壓縮包 tar xvzf mongodb-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 --port 21000 --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 --replSet shard1 --port 22001 --dbpath /data/mongodbtest/shard1/data --logpath /data/mongodbtest/shard1/log/shard1.log --fork --nojournal --oplogSize 10 |
為了快速啟動並節約測試環境儲存空間,這裡加上 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 --replSet shard3 --port 22003 --dbpath /data/mongodbtest/shard3/data --logpath /data/mongodbtest/shard3/log/shard3.log --fork --nojournal --oplogSize 10 |
分別對每個分片配置副本集,深入瞭解副本集參考本系列前幾篇文章。
任意登陸一個機器,比如登陸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 |
public class TestMongoDBShards { public static void main(String[] args) { try { List<ServerAddress> addresses = new ArrayList<ServerAddress>(); ServerAddress address1 = new ServerAddress("192.168.0.136" , 20000); ServerAddress address2 = new ServerAddress("192.168.0.137" , 20000); ServerAddress address3 = new ServerAddress("192.168.0.138" , 20000); addresses.add(address1); addresses.add(address2); addresses.add(address3); MongoClient client = new MongoClient(addresses); DB db = client.getDB( "testdb" ); DBCollection coll = db.getCollection( "table1" ); BasicDBObject object = new BasicDBObject(); object.append( "id" , 1); DBObject dbObject = coll.findOne(object); System. out .println(dbObject); } catch (Exception e) { e.printStackTrace(); } } } |
整個分片叢集搭建完了,思考一下我們這個架構是不是足夠好呢?其實還有很多地方需要優化,比如我們把所有的仲裁節點放在一臺機器,其餘兩臺機器承擔了全部讀寫操作,但是作為仲裁的192.168.0.138相當空閒。讓機器3 192.168.0.138多分擔點責任吧!架構可以這樣調整,把機器的負載分的更加均衡一點,每個機器既可以作為主節點、副本節點、仲裁節點,這樣壓力就會均衡很多了,如圖:
當然生產環境的資料遠遠大於當前的測試資料,大規模資料應用情況下我們不可能把全部的節點像這樣部署,硬體瓶頸是硬傷,只能擴充套件機器。要用好mongodb還有很多機制需要調整,不過通過這個東東我們可以快速實現高可用性、高擴充套件性,所以它還是一個非常不錯的Nosql元件。
再看看我們使用的mongodb java 驅動客戶端 MongoClient(addresses),這個可以傳入多個mongos 的地址作為mongodb叢集的入口,並且可以實現自動故障轉移,但是負載均衡做的好不好呢?開啟原始碼檢視:
它的機制是選擇一個ping 最快的機器來作為所有請求的入口,如果這臺機器掛掉會使用下一臺機器。那這樣。。。。肯定是不行的!萬一出現雙十一這樣的情況所有請求集中傳送到這一臺機器,這臺機器很有可能掛掉。一但掛掉了,按照它的機制會轉移請求到下臺機器,但是這個壓力總量還是沒有減少啊!下一臺還是可能崩潰,所以這個架構還有漏洞!不過這個文章已經太長了,後續解決吧。