MongoDb集羣

NOSQL優勢:

  • 大數據量,可以通過廉價服務器存儲大量的數據。
  • 高擴展性,Nosql去掉了關係數據庫的關係型特性,很容易橫向擴展。
  • 高性能,Nosql通過簡單的key-value方式獲取數據,非常快速。
  • 靈活的數據模型,NoSQL無需事先爲要存儲的數據建立字段,隨時可以存儲自定義的數據格式。
  • 高可用,NoSQL在不太影響性能的情況,就可以方便的實現高可用的架構。


一、mongodb單實例。(生產環境很少用到,詳見上一篇文章)

二、主從模式。(已不推薦使用)

使用mysql等傳統關係型數據庫時大家廣泛用到,採用雙機備份後主節點掛掉了後從節點可以接替主機繼續服務。

 

下面看一下怎麼一步步搭建一個mongodb的主從複製節點:

1. 準備兩臺機器 192.168.0.1 和 192.168.0.2。 192.168.0.1 當作主節點, 192.168.0.2作爲從節點。

2. 分別下載mongodb安裝程序包。

3. 在192.168.0.1啓動mongodb主節點程序。注意後面的這個 “ –master ”參數,標示主節點。

mongod -f mongodb.master

master=true 
dbpath = /usr/local/mongodb2.6.3/mongodb_data/config/data 
logpath = /usr/local/mongodb2.6.3/mongodb_data/config/logs/mongodb.log 
logappend = true 
directoryperdb = true 
noauth = true 
port = 27017
maxConns = 1024 
fork = true 
quota = true 
quotaFiles = 1024 
nssize = 16 


4. 在192.168.0.2啓動mongodb從節點程序。

mongod -f mongodb.slave

slave=true 
source=192.168.0.180:27017
dbpath = /usr/local/mongodb2.6.3/mongodb_data/config/data 
logpath = /usr/local/mongodb2.6.3/mongodb_data/config/logs/mongodb.log 
logappend = true 
directoryperdb = true 
noauth = true 
port = 27017
maxConns = 1024 
fork = true 
quota = true 
quotaFiles = 1024 
nssize = 16 


5. 測試主從複製。

在主節點上連接到終端:

mongo --port 27017

use users;

>db.users.save({age:22});

>db.users.find();

可以看到主機的同步日誌

[initandlisten] connection accepted from 192.168.0.2:37285 #3 (2 connections now open) 
[slaveTracking] update local.slaves query: { _id: ObjectId(’5284e6268ed115d6238bdb39′), config: { host: “192.168.0.2:35271″, upgradeNeeded: true }, ns: “local.oplog.$main” } update: { $set: { syncedTo: Timestamp 1384441570000|1 } } nscanned:1 nupdated:1 fastmod:1 keyUpdates:0 locks(micros) w:132015 132ms

檢查從機的數據。

mongo --port 27017

show dbs;

use users;

db.users.find();

查詢後數據已經同步過來了。再看看日誌,發現從主機確實從主機同步了數據。

到此主從結構的mongodb搭建好了。 


故障轉移測試
,現在兩臺服務器如果主服務器掛掉了,從服務器可以正常運轉嗎?

a. 先測試下從服務器可以當成主服務器嗎,也就是往從服務器裏寫能夠同步主服務器嗎?在192.168.0.2上連接mongodb。

  1. mongo 127.0.0.1:27017  
  2. > db.testdb.insert({"test3":"testval3"});  
  3. not master  
可以看到 mongodb的從節點是不能提供寫操作的,只能提供讀操作。

b. 如果從服務器掛掉,主服務器還可以提供服務。如果主服務器掛掉了從服務器能否自動變爲可寫。
測試一下!

先殺掉原來的mongodb主服務器。

  1. kill -3 `ps -ef|grep mongod|grep -v grep|awk '{print $2}'`  
測試從服務器能否可寫。在192.168.0.2上連接mongodb測試。

  1. > db.testdb.insert({"test3":"testval3"});  
  2. not master  

看起來從服務器沒有自動接替主服務器的功能,只有手工處理了!

停止從服務器,在原數據文件啓動並添加主服務器標示。

  1. mongod  --dbpath /data/mongodbtest/slave --master  
等到啓動成功(時間有點長)。在192.168.0.2 上 連接

  1. mongo 192.168.0.2:27017  
  1. > db.testdb.find();  
  2. "_id" : ObjectId("5288629e9b0318be4b20bd4c"), "test1" : "testval1" }  
  3. "_id" : ObjectId("528862d69b0318be4b20bd4d"), "test2" : "testval2" }  

成功! 

爲了方便測試,在192.168.0.2上再建立一個文件夾 /data/mongodbtest/slave1 作爲另一個slave服務器。啓動slave2服務,

  1. mongod  --dbpath /data/mongodbtest/slave1 --slave  --port 27017 --source 192.168.0.1:27017。  
成功啓動後通過mongodb連接測試:

  1. > db.testdb.find();  
  2. "_id" : ObjectId("5288629e9b0318be4b20bd4c"), "test1" : "testval1" }  
  3. "_id" : ObjectId("528862d69b0318be4b20bd4d"), "test2" : "testval2" }  
搭建了這套主從複製系統是不是就很穩健了,其實不然。。。看看這幾個問題?

  • 主節點掛了能否自動切換連接?目前需要手工切換。
  • 主節點的寫壓力過大如何解決?
  • 從節點每個上面的數據都是對數據庫全量拷貝,從節點壓力會不會過大?
  • 就算對從節點路由實施路由訪問策略能否做到自動擴展?
主從可能遇到的問題:
log日誌輸出:local.source 192.168.0.107:27017!=host:"192.168.0.180:27017
原因是設置的主節點與當前庫中的source不同,需要更新local庫中的source,具體如下:
use local
db.source.update({host:"192.168.0.107:27017"},{$set{host:"192.168.0.180:27017"}})


二、副本集:(主從的替代方式)

mongoDB官方已經不建議使用主從模式了,替代方案是採用副本集的模式,如圖: 


1、準備兩臺機器 192.168.1.136、192.168.1.137、192.168.1.138。 192.168.1.136 當作副本集主節點,192.168.1.137、192.168.1.138作爲副本集副本節點

2、分別在每臺機器上建立mongodb副本集測試文件夾

#存放整個mongodb文件
mkdir -p /data/mongodbtest/replset 

#存放mongodb數據文件
mkdir -p /data/mongodbtest/replset/data

#進入mongodb文件夾
cd  /data/mongodbtest

3、下載mongodb的安裝程序包

wget <a href="http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz">http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.4.8.tgz</a>

注意linux生產環境不能安裝32位的mongodb,因爲32位受限於操作系統最大2G的文件限制。

mongorep4

#解壓下載的壓縮包  
tar xvzf mongodb-linux-x86_64-2.4.8.tgz

4、分別在每臺機器上啓動mongodb

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongod  --dbpath /data/mongodbtest/replset/data   --replSet repset 

可以看到控制檯上顯示副本集還沒有配置初始化信息。

[plain] view plaincopy
  1. Sun Dec 29 20:12:02.953 [rsStart] replSet can't get local.system.replset config from self or any seed (EMPTYCONFIG)  
  2. Sun Dec 29 20:12:02.953 [rsStart] replSet info you may need to run  replSetInitiate -- rs.initiate() in the shell -- if that is not already done  

5、初始化副本集

在三臺機器上任意一臺機器登陸mongodb

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo

#使用admin數據庫
use admin

#定義副本集配置變量,這裏的 _id:”repset” 和上面命令參數“ –replSet repset” 要保持一樣。

config = { _id:"repset", members:[
... {_id:0,host:"192.168.1.136:27017"},
... {_id:1,host:"192.168.1.137:27017"},
... {_id:2,host:"192.168.1.138:27017"}]
... }

#輸出

[plain] view plaincopy
  1. {  
  2.         "_id" : "repset",  
  3.         "members" : [  
  4.                 {  
  5.                         "_id" : 0,  
  6.                         "host" : "192.168.1.136:27017"  
  7.                 },  
  8.                 {  
  9.                         "_id" : 1,  
  10.                         "host" : "192.168.1.137:27017"  
  11.                 },  
  12.                 {  
  13.                         "_id" : 2,  
  14.                         "host" : "192.168.1.138:27017"  
  15.                 }  
  16.         ]  
  17. }  
#初始化副本集配置
rs.initiate(config);

#輸出成功

[plain] view plaincopy
  1. {  
  2.         "info" : "Config now saved locally.  Should come online in about a minute.",  
  3.         "ok" : 1  
  4. }  

#查看日誌,副本集啓動成功後,138爲主節點PRIMARY,136、137爲副本節點SECONDARY

#查看集羣節點的狀態 rs.status();

#輸出

[plain] view plaincopy
  1. {  
  2.         "set" : "repset",  
  3.         "date" : ISODate("2013-12-29T12:54:25Z"),  
  4.         "myState" : 1,  
  5.         "members" : [  
  6.                 {  
  7.                         "_id" : 0,  
  8.                         "name" : "192.168.1.136:27017",  
  9.                         "health" : 1,  
  10.                         "state" : 2,  
  11.                         "stateStr" : "SECONDARY",  
  12.                         "uptime" : 1682,  
  13.                         "optime" : Timestamp(1388319973, 1),  
  14.                         "optimeDate" : ISODate("2013-12-29T12:26:13Z"),  
  15.                         "lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),  
  16.                         "lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),  
  17.                         "pingMs" : 1,  
  18.                         "syncingTo" : "192.168.1.138:27017"  
  19.                 },  
  20.                 {  
  21.                         "_id" : 1,  
  22.                         "name" : "192.168.1.137:27017",  
  23.                         "health" : 1,  
  24.                         "state" : 2,  
  25.                         "stateStr" : "SECONDARY",  
  26.                         "uptime" : 1682,  
  27.                         "optime" : Timestamp(1388319973, 1),  
  28.                         "optimeDate" : ISODate("2013-12-29T12:26:13Z"),  
  29.                         "lastHeartbeat" : ISODate("2013-12-29T12:54:25Z"),  
  30.                         "lastHeartbeatRecv" : ISODate("2013-12-29T12:54:24Z"),  
  31.                         "pingMs" : 1,  
  32.                         "syncingTo" : "192.168.1.138:27017"  
  33.                 },  
  34.                 {  
  35.                         "_id" : 2,  
  36.                         "name" : "192.168.1.138:27017",  
  37.                         "health" : 1,  
  38.                         "state" : 1,  
  39.                         "stateStr" : "PRIMARY",  
  40.                         "uptime" : 2543,  
  41.                         "optime" : Timestamp(1388319973, 1),  
  42.                         "optimeDate" : ISODate("2013-12-29T12:26:13Z"),  
  43.                         "self" : true  
  44.                 }  
  45.         ],  
  46.         "ok" : 1  
  47. }  

整個副本集已經搭建成功了。

6、測試副本集數據複製功能

#在主節點192.168.1.138 上連接到終端:
mongo 127.0.0.1

#建立test 數據庫。
use test;

往testdb表插入數據。
> db.testdb.insert({"test1":"testval1"})

#在副本節點 192.168.1.136、192.168.1.137 上連接到mongodb查看數據是否複製過來。
/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 192.168.1.136:27017

#使用test 數據庫。
repset:SECONDARY> use test;

repset:SECONDARY> show tables;

#輸出

[plain] view plaincopy
  1. Sun Dec 29 21:50:48.590 error: { "$err" : "not master and slaveOk=false", "code" : 13435 } at src/mongo/shell/query.js:128  
          
#mongodb默認是從主節點讀寫數據的,副本節點上不允許讀,需要設置副本節點可以讀。
repset:SECONDARY> db.getMongo().setSlaveOk();

#可以看到數據已經複製到了副本集。
repset:SECONDARY> db.testdb.find();
[plain] view plaincopy
  1. #輸出  
  2. { "_id" : ObjectId("52c028460c7505626a93944f"), "test1" : "testval1" }  

7、測試副本集故障轉移功能

先停掉主節點mongodb 138,查看136、137的日誌可以看到經過一系列的投票選擇操作,137 當選主節點,136從137同步數據過來。

查看整個集羣的狀態,可以看到138爲狀態不可達。

/data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 192.168.1.136:27017

repset:SECONDARY> rs.status();

再啓動原來的主節點 138,發現138 變爲 SECONDARY,還是137 爲主節點 PRIMARY。

8、java程序連接副本集測試。三個節點有一個節點掛掉也不會影響應用程序客戶端對整個副本集的讀寫!

List<ServerAddress> addresses = new ArrayList<ServerAddress>();
ServerAddress address1 = new ServerAddress("192.168.0.180" , 27017); 
ServerAddress address2 = new ServerAddress("192.168.0.181" , 27017); 
ServerAddress address3 = new ServerAddress("192.168.0.182" , 27017); 
addresses.add(address1);
addresses.add(address2); 
addresses.add(address3);
Mongo mg = new MongoClient(addresses);
DB db = mg.getDB("qg_cache");


目前看起來支持完美的故障轉移了,這個架構是不是比較完美了?其實還有很多地方可以優化,比如開頭的第二個問題:主節點的讀寫壓力過大如何解決?常見的解決方案是讀寫分離,mongodb副本集的讀寫分離如何做呢?

看圖說話:

mongorep5

常規寫操作來說並沒有讀操作多,所以一臺主節點負責寫,兩臺副本節點負責讀。

1、設置讀寫分離需要先在副本節點SECONDARY 設置 setSlaveOk。 
2、在程序中設置副本節點負責讀操作,如下代碼:

[java] view plaincopy
  1. public class TestMongoDBReplSetReadSplit {  
  2.   
  3.         public static void main(String[] args) {  
  4.   
  5.                try {  
  6.                      List<ServerAddress> addresses = new ArrayList<ServerAddress>();  
  7.                      ServerAddress address1 = new ServerAddress("192.168.1.136" , 27017);  
  8.                      ServerAddress address2 = new ServerAddress("192.168.1.137" , 27017);  
  9.                      ServerAddress address3 = new ServerAddress("192.168.1.138" , 27017);  
  10.                      addresses.add(address1);  
  11.                      addresses.add(address2);  
  12.                      addresses.add(address3);  
  13.   
  14.                      MongoClient client = new MongoClient(addresses);  
  15.                      DB db = client.getDB( "test" );  
  16.                      DBCollection coll = db.getCollection( "testdb" );  
  17.   
  18.   
  19.                      BasicDBObject object = new BasicDBObject();  
  20.                      object.append( "test2" , "testval2" );  
  21.   
  22.                       //讀操作從副本節點讀取  
  23.                      ReadPreference preference = ReadPreference. secondary();  
  24.                      DBObject dbObject = coll.findOne(object, null , preference);  
  25.   
  26.                      System. out .println(dbObject);  
  27.   
  28.   
  29.               } catch (Exception e) {  
  30.                      e.printStackTrace();  
  31.               }  
  32.        }  
  33. }  

讀參數除了secondary一共還有五個參數:primary、primaryPreferred、secondary、secondaryPreferred、nearest。

mongorep6

primary:默認參數,只從主節點上進行讀取操作; 
primaryPreferred:大部分從主節點上讀取數據,只有主節點不可用時從secondary節點讀取數據。 
secondary:只從secondary節點上進行讀取操作,存在的問題是secondary節點的數據會比primary節點數據“舊”。 
secondaryPreferred:優先從secondary節點進行讀取操作,secondary節點不可用時從主節點讀取數據; 
nearest:不管是主節點、secondary節點,從網絡延遲最低的節點上讀取數據。

好,讀寫分離做好我們可以數據分流,減輕壓力解決了“主節點的讀寫壓力過大如何解決?”這個問題。不過當我們的副本節點增多時,主節點的複製壓力會加大有什麼辦法解決嗎?mongodb早就有了相應的解決方案。

看圖: 

其中的仲裁節點不存儲數據,只是負責故障轉移的羣體投票,這樣就少了數據複製的壓力。是不是想得很周到啊,一看mongodb的開發兄弟熟知大數據架構體系,其實不只是主節點、副本節點、仲裁節點,還有Secondary-Only、Hidden、Delayed、Non-Voting。

Secondary-Only:不能成爲primary節點,只能作爲secondary副本節點,防止一些性能不高的節點成爲主節點。 
Hidden:這類節點是不能夠被客戶端制定IP引用,也不能被設置爲主節點,但是可以投票,一般用於備份數據。 
Delayed:可以指定一個時間延遲從primary節點同步數據。主要用於備份數據,如果實時同步,誤刪除數據馬上同步到從節點,恢復又恢復不了。 
Non-Voting:沒有選舉權的secondary節點,純粹的備份數據節點。

到此整個mongodb副本集搞定了兩個問題:

  • 主節點掛了能否自動切換連接?目前需要手工切換。
  • 主節點的讀寫壓力過大如何解決?

還有這兩個問題後續解決:

  • 從節點每個上面的數據都是對數據庫全量拷貝,從節點壓力會不會過大?
  • 數據壓力大到機器支撐不了的時候能否做到自動擴展?

做了副本集發現又一些問題:

  • 副本集故障轉移,主節點是如何選舉的?能否手動干涉下架某一臺主節點。
  • 官方說副本集數量最好是奇數,爲什麼?
  • mongodb副本集是如何同步的?如果同步不及時會出現什麼情況?會不會出現不一致性?
  • mongodb的故障轉移會不會無故自動發生?什麼條件會觸發?頻繁觸發可能會帶來系統負載加重

三、分片方式:(主要解決單獨的節點數據量過大的問題)

在系統早期,數據量還小的時候不會引起太大的問題,但是隨着數據量持續增多,後續遲早會出現一臺機器硬件瓶頸問題的。而MongoDB主打的就是海量數據架構,他不能解決海量數據怎麼行!“分片”就用這個來解決這個問題。

  傳統數據庫怎麼做海量數據讀寫?其實一句話概括:分而治之。上圖看看就清楚了,如下TaoBao嶽旭強提到的架構圖:

  

fenpian1

 

  上圖中有個TDDL,是TaoBao的一個數據訪問層組件,他主要的作用是SQL解析、路由處理。根據應用的請求的功能解析當前訪問的sql判斷是在哪個業務數據庫、哪個表訪問查詢並返回數據結果。具體如圖:

  

fenpian2

 

  說了這麼多傳統數據庫的架構,那NoSQL怎麼去做到了這些呢?MySQL要做到自動擴展需要加一個數據訪問層用程序去擴展,數據庫的增加、刪除、備份還需要程序去控制。一但數據庫的節點一多,要維護起來也是非常頭疼的。不過MongoDB所有的這一切通過他自己的內部機制就可以搞定!還是上圖看看MongoDB通過哪些機制實現路由、分片:

  

fenpian3

 

  從圖中可以看到有四個組件:mongos、config server、shard、replica set。

  mongos,數據庫集羣請求的入口,所有的請求都通過mongos進行協調,不需要在應用程序添加一個路由選擇器,mongos自己就是一個請求分發中心,它負責把對應的數據請求請求轉發到對應的shard服務器上。在生產環境通常有多mongos作爲請求的入口,防止其中一個掛掉所有的mongodb請求都沒有辦法操作。

  config server,顧名思義爲配置服務器,存儲所有數據庫元信息(路由、分片)的配置。mongos本身沒有物理存儲分片服務器和數據路由信息,只是緩存在內存裏,配置服務器則實際存儲這些數據。mongos第一次啓動或者關掉重啓就會從 config server 加載配置信息,以後如果配置服務器信息變化會通知到所有的 mongos 更新自己的狀態,這樣 mongos 就能繼續準確路由。在生產環境通常有多個 config server 配置服務器,因爲它存儲了分片路由的元數據,這個可不能丟失!就算掛掉其中一臺,只要還有存貨, mongodb集羣就不會掛掉。

  shard,這就是傳說中的分片了。上面提到一個機器就算能力再大也有天花板,就像軍隊打仗一樣,一個人再厲害喝血瓶也拼不過對方的一個師。俗話說三個臭皮匠頂個諸葛亮,這個時候團隊的力量就凸顯出來了。在互聯網也是這樣,一臺普通的機器做不了的多臺機器來做,如下圖:

  

fenpian4

 

  一臺機器的一個數據表 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臺機器,在同一臺機器只要端口不同就可以,看一下物理部署圖:

  

fenpian5

 

  架構搭好了,安裝軟件!

  1. 準備機器,IP分別設置爲: 192.168.0.136、192.168.0.137、192.168.0.138。

  2. 分別在每臺機器上建立mongodb分片對應測試文件夾。

#存放mongodb數據文件
  mkdir -p /data/mongodbtest
  #進入mongodb文件夾
  cd /data/mongodbtest

  3. 下載mongodb的安裝程序包

  4. 分別在每臺機器建立mongos 、config 、 shard1 、shard2、shard3 五個目錄。

  因爲mongos不存儲數據,只需要建立日誌文件目錄即可。

  #建立mongos目錄

  mkdir -p /usr/local/mongodb2.6.3/mongos/log

  #建立mongos數據文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/mongos/data

  #建立config server 日誌文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/mongod/log

  #建立config server 日誌文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/mongodb/log

  #建立shard1 數據文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/shard1/data

  #建立shard1 日誌文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/shard1/log

  #建立shard2 數據文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/shard2/data

  #建立shard2 日誌文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/shard2/log

  #建立shard3 數據文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/shard3/data

  #建立shard3 日誌文件存放目錄

  mkdir -p /usr/local/mongodb2.6.3/shard3/log

  5. 規劃5個組件對應的端口號,由於一個機器需要同時部署 mongos、config server 、shard1、shard2、shard3,所以需要用端口進行區分。

  這個端口可以自由定義,在本文 mongos爲 27000, config server 爲 27017, shard1爲 27001 , shard2爲27002, shard3爲27003.

  6. 在每一臺服務器分別啓動配置服務器。

configsvr = true
dbpath = /usr/local/mongodb2.6.3/mongodb_data/mongod/data 
logpath = /usr/local/mongodb2.6.3/mongodb_data/mongod/logs/mongodb.log 
logappend = true 
directoryperdb = true 
noauth = true 
port = 27017
maxConns = 1024 
fork = true 
quota = true 
quotaFiles = 1024 
nssize = 16 

  7. 在每一臺服務器分別啓動mongos服務器。

configdb = 192.168.0.180:27017,192.168.0.181:27017,192.168.0.182:27017
logpath = /usr/local/mongodb2.6.3/mongodb_data/mongos/logs/mongodb.log 
logappend = true 
port = 27000
maxConns = 1024 
fork = true 

  8. 配置各個分片的副本集。

  #在每個機器裏分別設置分片1服務器及副本集shard1

shardsvr = true
replSet = shard1
dbpath = /usr/local/mongodb2.6.3/mongodb_data/shard1/data 
logpath = /usr/local/mongodb2.6.3/mongodb_data/shard1/logs/mongodb.log 
logappend = true 
port = 27001
maxConns = 800 
fork = true 
smallfiles = true

  #在每個機器裏分別設置分片2服務器及副本集shard2

shardsvr = true
replSet = shard1
dbpath = /usr/local/mongodb2.6.3/mongodb_data/shard1/data 
logpath = /usr/local/mongodb2.6.3/mongodb_data/shard1/logs/mongodb.log 
logappend = true 
port = 27001
maxConns = 800 
fork = true 
smallfiles = true

  #在每個機器裏分別設置分片3服務器及副本集shard3

shardsvr = true
replSet = shard1
dbpath = /usr/local/mongodb2.6.3/mongodb_data/shard1/data 
logpath = /usr/local/mongodb2.6.3/mongodb_data/shard1/logs/mongodb.log 
logappend = true 
port = 27001
maxConns = 800 
fork = true 
smallfiles = true

  任意登陸一個機器,比如登陸192.168.0.136,連接MongoDB

  #設置第一個分片副本集

  /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:27001

  #使用admin數據庫

  use admin

  #定義副本集配置

  config = { _id:"shard1", members:[

  {_id:0,host:"192.168.0.136:27001"},

  {_id:1,host:"192.168.0.137:27001"},

  {_id:2,host:"192.168.0.138:27001",arbiterOnly:true}

  ]

  }

  #初始化副本集配置

  rs.initiate(config);

  #設置第二個分片副本集

  /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:27002

  #使用admin數據庫

  use admin

  #定義副本集配置

  config = { _id:"shard2", members:[

  {_id:0,host:"192.168.0.136:27002"},

  {_id:1,host:"192.168.0.137:27002"},

  {_id:2,host:"192.168.0.138:27002",arbiterOnly:true}

  ]

  }

  #初始化副本集配置

  rs.initiate(config);

  #設置第三個分片副本集

  /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:27003

  #使用admin數據庫

  use admin

  #定義副本集配置

  config = { _id:"shard3", members:[

  {_id:0,host:"192.168.0.136:27003"},

  {_id:1,host:"192.168.0.137:27003"},

  {_id:2,host:"192.168.0.138:27003",arbiterOnly:true}

  ]

  }

  #初始化副本集配置

  rs.initiate(config);

  9. 目前搭建了mongodb配置服務器、路由服務器,各個分片服務器,不過應用程序連接到 mongos 路由服務器並不能使用分片機制,還需要在程序裏設置分片配置,讓分片生效。

  #連接到mongos /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:27000

  #使用admin數據庫 user admin

  #串聯路由服務器與分配副本集1

  db.runCommand( { addshard : "shard1/192.168.0.136:27001,192.168.0.137:27001,192.168.0.138:27001"});

  如裏shard是單臺服務器,用 db.runCommand( { addshard : “ [: ]” } )這樣的命令加入,如果shard是副本集,用db.runCommand( { addshard : “replicaSetName/ [:port][,serverhostname2[:port],…]” });這樣的格式表示 。

  #串聯路由服務器與分配副本集2

  db.runCommand( { addshard : "shard2/192.168.0.136:27002,192.168.0.137:27002,192.168.0.138:27002"});

  #串聯路由服務器與分配副本集3

  db.runCommand( { addshard : "shard3/192.168.0.136:27003,192.168.0.137:27003,192.168.0.138:27003"});

  #查看分片服務器的配置

  db.runCommand( { listshards : 1 } );

  #內容輸出

  [plain] view plaincopy{

  "shards" : [

  {

  "_id" : "shard1",

  "host" : "shard1/192.168.0.136:27001,192.168.0.137:27001"

  },

  {

  "_id" : "shard2",

  "host" : "shard2/192.168.0.136:27002,192.168.0.137:27002"

  },

  {

  "_id" : "shard3",

  "host" : "shard3/192.168.0.136:27003,192.168.0.137:27003"

  }

  ],

  "ok" : 1

  }

  因爲192.168.0.138是每個分片副本集的仲裁節點,所以在上面結果沒有列出來。

  10. 目前配置服務、路由服務、分片服務、副本集服務都已經串聯起來了,但我們的目的是希望插入數據,數據能夠自動分片,就差那麼一點點,一點點。。。

  連接在mongos上,準備讓指定的數據庫、指定的集合分片生效。

  #指定testdb分片生效

  db.runCommand( { enablesharding :"testdb"});

  #指定數據庫裏需要分片的集合和片鍵(片鍵是爲了設置當前記錄分佈在哪個分片中;這裏採用hashed是儘量保證各個分片的數據量平均)

  db.runCommand( { shardcollection : "testdb.table1",key : {"_id": "hashed"} } )

  我們設置testdb的 table1 表需要分片,根據“_id”自動分片到 shard1 ,shard2,shard3 上面去。要這樣設置是因爲不是所有mongodb 的數據庫和表 都需要分片!這裏的_id是mongodb自動生成的,不需要人爲指定

  11. 測試分片配置結果。

  #連接mongos服務器

  /data/mongodbtest/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:27000

  #使用testdb use testdb;

  #插入測試數據

  for (var i = 1; i <= 100000; i++)

  db.table1.save({id:i,"test1":"testval1"});

  #查看分片情況如下,部分無關信息省掉了

  db.table1.stats();

  [java] view plaincopy{

  "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作爲入口,就算其中哪個入口掛掉了都沒關係,使用集羣客戶端程序如下:

view plaincopypublic class TestMongoDBShards { 
public static void main(String[] args)
  { 
try { 
List addresses = new ArrayList();
  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多分擔點責任吧!架構可以這樣調整,把機器的負載分的更加均衡一點,每個機器既可以作爲主節點、副本節點、仲裁節點,這樣壓力就會均衡很多了,如圖:

  

fenpian6

 

  當然生產環境的數據遠遠大於當前的測試數據,大規模數據應用情況下我們不可能把全部的節點像這樣部署,硬件瓶頸是硬傷,只能擴展機器。要用好mongodb還有很多機制需要調整,不過通過這個東東我們可以快速實現高可用性、高擴展性,所以它還是一個非常不錯的Nosql組件。

  再看看我們使用的mongodb java 驅動客戶端 MongoClient(addresses),這個可以傳入多個mongos 的地址作爲mongodb集羣的入口,並且可以實現自動故障轉移,但是負載均衡做的好不好呢?打開源代碼查看:

  

fenpian7

 

   它的機制是選擇一個ping 最快的機器來作爲所有請求的入口,如果這臺機器掛掉會使用下一臺機器。那這樣。。。。肯定是不行的!萬一出現雙十一這樣的情況所有請求集中發送到這一臺機器,這臺機器很有可能掛掉。一但掛掉了,按照它的機制會轉移請求到下臺機器,但是這個壓力總量還是沒有減少啊!下一臺還是可能崩潰,所以這個架構還有漏洞!限於文章篇幅,請待後續解決。




參考:

http://www.csdn.net/article/2014-04-09/2819221-build-high-avialable-mongodb-cluster-part-1


發佈了156 篇原創文章 · 獲贊 11 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章