原文地址:http://www.lanceyan.com/tech/arch/mongodb_shard1.htmlhtml
在系統早期,數據量還小的時候不會引發太大的問題,可是隨着數據量持續增多,後續早晚會出現一臺機器硬件瓶頸問題的。而mongodb主打的就是海量數據架構,他不能解決海量數據怎麼行!不行!「分片」就用這個來解決這個問題。java
傳統數據庫怎麼作海量數據讀寫?其實一句話歸納:分而治之。上圖看看就清楚了,以下 taobao嶽旭強在infoq中提到的 架構圖:mysql
上圖中有個TDDL,是taobao的一個數據訪問層組件,他主要的做用是SQL解析、路由處理。根據應用的請求的功能解析當前訪問的sql判斷是在哪一個業務數據庫、哪一個表訪問查詢並返回數據結果。具體如圖:linux
說了這麼多傳統數據庫的架構,那Nosql怎麼去作到了這些呢?mysql要作到自動擴展須要加一個數據訪問層用程序去擴展,數據庫的增長、刪除、備份還須要程序去控制。一但數據庫的節點一多,要維護起來也是很是頭疼的。不過mongodb全部的這一切經過他本身的內部機制就能夠搞定!頓時石化了,這麼牛X!仍是上圖看看mongodb經過哪些機制實現路由、分片:sql
從圖中能夠看到有四個組件:mongos、config server、shard、replica set。mongodb
mongos,數據庫集羣請求的入口,全部的請求都經過mongos進行協調,不須要在應用程序添加一個路由選擇器,mongos本身就是一個請求分發中心,它負責把對應的數據請求請求轉發到對應的shard服務器上。在生產環境一般有多mongos做爲請求的入口,防止其中一個掛掉全部的mongodb請求都沒有辦法操做。數據庫
config server,顧名思義爲配置服務器,存儲全部數據庫元信息(路由、分片)的配置。mongos自己沒有物理存儲分片服務器和數據路由信息,只是緩存在內存裏,配置服務器則實際存儲這些數據。mongos第一次啓動或者關掉重啓就會從 config server 加載配置信息,之後若是配置服務器信息變化會通知到全部的 mongos 更新本身的狀態,這樣 mongos 就能繼續準確路由。在生產環境一般有多個 config server 配置服務器,由於它存儲了分片路由的元數據,這個可不能丟失!就算掛掉其中一臺,只要還有存貨, mongodb集羣就不會掛掉。緩存
shard,這就是傳說中的分片了。上面提到一個機器就算能力再大也有天花板,就像軍隊打仗同樣,一我的再厲害喝血瓶也拼不過對方的一個師。俗話說三個臭皮匠頂個諸葛亮,這個時候團隊的力量就凸顯出來了。在互聯網也是這樣,一臺普通的機器作不了的多臺機器來作,以下圖:bash
一臺機器的一個數據表 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
2
|
#存放mongodb數據文件
mkdir
-p
/data/mongodbtest
|
1
2
|
#進入mongodb文件夾
cd
/data/mongodbtest
|
1
|
wget http:
//fastdl
.mongodb.org
/linux/mongodb-linux-x86_64-2
.4.8.tgz
|
1
2
|
#解壓下載的壓縮包
tar
xvzf mongodb-linux-x86_64-2.4.8.tgz
|
1
2
|
#創建mongos目錄
mkdir
-p
/data/mongodbtest/mongos/log
|
1
2
|
#創建config server 數據文件存放目錄
mkdir
-p
/data/mongodbtest/config/data
|
1
2
|
#創建config server 日誌文件存放目錄
mkdir
-p
/data/mongodbtest/config/log
|
1
2
|
#創建config server 日誌文件存放目錄
mkdir
-p
/data/mongodbtest/mongos/log
|
1
2
|
#創建shard1 數據文件存放目錄
mkdir
-p
/data/mongodbtest/shard1/data
|
1
2
|
#創建shard1 日誌文件存放目錄
mkdir
-p
/data/mongodbtest/shard1/log
|
1
2
|
#創建shard2 數據文件存放目錄
mkdir
-p
/data/mongodbtest/shard2/data
|
1
2
|
#創建shard2 日誌文件存放目錄
mkdir
-p
/data/mongodbtest/shard2/log
|
1
2
|
#創建shard3 數據文件存放目錄
mkdir
-p
/data/mongodbtest/shard3/data
|
1
2
|
#創建shard3 日誌文件存放目錄
mkdir
-p
/data/mongodbtest/shard3/log
|
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
|
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
|
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
|
#設置第一個分片副本集
/data/mongodbtest/mongodb-linux-x86_64-2
.4.8
/bin/mongo
127.0.0.1:22001
|
1
2
|
#使用admin數據庫
use admin
|
1
2
3
4
5
6
7
|
#定義副本集配置
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
}
]
}
|
1
2
|
#初始化副本集配置
rs.initiate(config);
|
1
2
|
#設置第二個分片副本集
/data/mongodbtest/mongodb-linux-x86_64-2
.4.8
/bin/mongo
127.0.0.1:22002
|
1
2
|
#使用admin數據庫
use admin
|
1
2
3
4
5
6
7
|
#定義副本集配置
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
}
]
}
|
1
2
|
#初始化副本集配置
rs.initiate(config);
|
1
2
|
#設置第三個分片副本集
/data/mongodbtest/mongodb-linux-x86_64-2
.4.8
/bin/mongo
127.0.0.1:22003
|
1
2
|
#使用admin數據庫
use admin
|
1
2
3
4
5
6
7
|
#定義副本集配置
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
}
]
}
|
1
2
|
#初始化副本集配置
rs.initiate(config);
|
1
2
|
#鏈接到mongos
/data/mongodbtest/mongodb-linux-x86_64-2
.4.8
/bin/mongo
127.0.0.1:20000
|
1
2
|
#使用admin數據庫
user admin
|
1
2
|
#串聯路由服務器與分配副本集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是每一個分片副本集的仲裁節點,因此在上面結果沒有列出來。
鏈接在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 的數據庫和表 都須要分片!
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。已經成功了!不過度的好像不是很均勻,因此這個分片仍是頗有講究的,後續再深刻討論。
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 最快的機器來做爲全部請求的入口,若是這臺機器掛掉會使用下一臺機器。那這樣。。。。確定是不行的!萬一出現雙十一這樣的狀況全部請求集中發送到這一臺機器,這臺機器頗有可能掛掉。一但掛掉了,按照它的機制會轉移請求到下臺機器,可是這個壓力總量仍是沒有減小啊!下一臺仍是可能崩潰,因此這個架構還有漏洞!不過這個文章已經太長了,後續解決吧。
參考資料:
http://www.cnblogs.com/bethal/p/5530698.html
https://docs.mongodb.com/manual/sharding/