2016年6月19日 星期日

MongoDB Sharding Cluster Tutorial

壹、環境

因為是只是練習環境,我開了4台虛擬機 (com6, com7, com8, com9) 進行測試
作業系統皆為CentOS 7
com6: 192.168.1.1
com7: 192.168.1.2
com8: 192.168.1.3
com9: 192.168.1.4


貳、Sharding Cluster構想

預計由三組Replica Set,一個Config Server,一個Routing Server (mongos) 組成
因此
com6: ("rs0/192.168.1.1:27017,192.168.1.1:27018,192.168.1.1:27019")
com7: ("rs1/192.168.1.2:27017,192.168.1.2:27018,192.168.1.2:27019")
com8: ("rs2/192.168.1.3:27017,192.168.1.3:27018,192.168.1.3:27019")
com9: Config Server: ("192.168.1.4:27017") , Routing Server: ("192.168.1.4:27018")


參、步驟

一、安裝MongoDB

1. MongoDB的官方軟體庫包含有五個軟體包(packages)
(1) mongodb-org                  這是一個metapackage,可以透過安裝這個自動安裝其他四個軟體包
(2) mongodb-org-server       mongod伺服器與設定檔
(3) mongodb-org-mongos    mongos伺服器.
(4) mongodb-org-shell         mongo操作介面(shell)

(5) mongodb-org-tools         包括了所有跟mognodb有關的小工具

2. 因為CentOS預設的Yum伺服器中沒有MongoDB軟體庫(Repository)的相關資訊,因此必須先新增Repos設定檔
#vi /etc/yum.repos.d/mongodb-org-3.2.repo
[mongodb-org-3.4]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.4/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.4.asc
3. 新增完之後就可以使用Yum安裝了
#yum install –y mongodb-org


二、設定MongoDB


在構想階段,有com6, com7 com8分別為三個MongoDB實體的Replica Set,因此設定設定檔、資料庫目錄、記錄檔共要三組
而com9為Config Server與Routing Server所以設定檔跟記錄檔要兩份,但Routing Server本身不儲存任何資料,因此只需要替Config Server建立一個資料庫目錄即可

1.  複製設定檔
com6, com7, com8 都要執行以下指令,將設定檔複製三分,分別為mongod1.conf, mongod2.conf, mongod3,conf
#mv /etc/mongod.conf /etc/mongod1.conf
#cp /etc/mongod1.conf /etc/mongod2.conf
#cp /etc/mongod1.conf /etc/mongod3.conf

com9裡面有兩個MongoDB實體,因此要兩份設定檔,分別為mongod-configserv.conf與mongod-routingserv.conf
#mv /etc/mongod.conf /etc/mongod-configserv.conf
#cp /etc/mongod1.conf /etc/mongod-routingserv.conf

2. 修改設定檔

2.1. com6, com7, com8設定
#vim /etc/mongod1.conf
修改將第10行的path: /var/log/mongodb/mongod.log
將com6的mongod.log改為mongod1.log
將com7的mongod.log改為mongod1.log
將com8的mongod.log改為mongod1.log

修改第14行的dbPath: /var/lib/mongo
將com6的mongo改為dbPath: /var/lib/mongo1
將com7的mongo改為dbPath: /var/lib/mongo1
將com8的mongo改為dbPath: /var/lib/mongo1


註解第29行的bindIp: 127.0.0.1
將com6的bindIp註解起來,變為#bindIp
將com7的bindIp註解起來,變為#bindIp
將com8的bindIp註解起來,變為#bindIp

取消第36#replication的註解
將com6的#replication: 取消註解,變成replication: 
將com7的#replication: 取消註解,變成replication: 
將com8的#replication: 取消註解,變成replication: 

在第37行新增Replica Set的名稱(  replSetName: )
com6為  replSetName: rs0
com7為  replSetName: rs1
com8為  replSetName: rs2
注意:replSetName前面空兩格,:後面空一格,不然會報錯

#vim /etc/mongod2.conf
修改將第10行的path: /var/log/mongodb/mongod.log
將com6的mongod.log改為mongod2.log
將com7的mongod.log改為mongod2.log
將com8的mongod.log改為mongod2.log

修改第14行的dbPath: /var/lib/mongo
將com6的mongo改為dbPath: /var/lib/mongo2
將com7的mongo改為dbPath: /var/lib/mongo2
將com8的mongo改為dbPath: /var/lib/mongo2

修改第28行的port: 27017
com6改為port: 27018
com7改為port: 27018

com8改為port: 27018


註解第29行的bindIp: 127.0.0.1
將com6的bindIp註解起來,變為#bindIp
將com7的bindIp註解起來,變為#bindIp
將com8的bindIp註解起來,變為#bindIp

取消第36#replication的註解
將com6的#replication: 取消註解,變成replication: 
將com7的#replication: 取消註解,變成replication: 
將com8的#replication: 取消註解,變成replication: 

在第37行新增Replica Set的名稱(  replSetName: )
com6為  replSetName: rs0
com7為  replSetName: rs1
com8為  replSetName: rs2
注意:replSetName前面空兩格,:後面空一格,不然會報錯


#vim /etc/mongod3.conf
修改將第10行的path: /var/log/mongodb/mongod.log
將com6的mongod.log改為mongod3.log
將com7的mongod.log改為mongod3.log
將com8的mongod.log改為mongod3.log

修改第14行的dbPath: /var/lib/mongo
將com6的mongo改為dbPath: /var/lib/mongo3
將com7的mongo改為dbPath: /var/lib/mongo3
將com8的mongo改為dbPath: /var/lib/mongo3

修改第28行的port: 27017
com6改為port: 27019
com7改為port: 27019

com8改為port: 27019


註解第29行的bindIp: 127.0.0.1
將com6的bindIp註解起來,變為#bindIp
將com7的bindIp註解起來,變為#bindIp
將com8的bindIp註解起來,變為#bindIp

取消第36#replication的註解
將com6的#replication: 取消註解,變成replication: 
將com7的#replication: 取消註解,變成replication: 
將com8的#replication: 取消註解,變成replication: 

在第37行新增Replica Set的名稱(  replSetName: )
com6為  replSetName: rs0
com7為  replSetName: rs1
com8為  replSetName: rs2
注意:replSetName前面空兩格,:後面空一格,不然會報錯

2.2. com9設定

2.2.1. config server
#vim mongo-config.conf
將第10行的path: /var/log/mongodb/mongod 改為path: /var/log/mongodb/mongod-configserv.log
將第14行的dbPath: /var/lib/mongo改為 dbPath: /var/lib/mongo-configserv
將第29行的bindIp: 127.0.0.1註解掉,改為#bindIp:  127.0.0.1
將第38行的#sharding取消註解,變為sharding
在第39行打上  clusterRole: configsvr
(clusterRole前面空兩格,:後空一格,不然會報錯)

2.2.2. routing server(mongos)
#vim /etc/mongo-routingserv.conf
將第10行的path: /var/log/mongodb/mongod 改為path: /var/log/mongodb/mongod-routingserv.log
將13~16行的與storage與旗下之選項全部註解掉
將第28行的,port:  27017改為port:  27018
將第29行的bindIp: 127.0.0.1註解掉,改為#bindIp:  127.0.0.1
將第38行的#sharding取消註解,變為sharding
在第39行打上  configDB: 192.168.1.4:27017
帶第40行打上  chunkSize: 64
(configDB與chunkSize前面空兩格,:後面空一格,不然會報錯)

3. 建立資料庫目錄

3.1. com6, com7, com8
#mv /var/lib/mongo /var/lib/mongo1
#mkdir mongo2
#mkdir mongo3
#chown mongod:mongod mongo2
#chown mongod:mongod mongo3


3.2. com9
#mv /var/lib/mongo /var/lib/mongo-configserv

三、建立Sharding Cluster

1. 建立Replica Set

1.1. 啟動mongod
在com6, com7, com8都要執行mongod
#mongod -f /etc/mongod1.conf
#mongod -f /etc/mongod2.conf
#mongod -f /etc/mongod3.conf
-f 的意思是使用設定檔執行,後面接設定檔路徑

1.2. 初始化Replica Set
假設三組Replica Set皆要以使用27017這個port的mongod作為為Primary
在com6, com7, com8執行mongo客戶端
#mongo --port 27017
--port後面接的port是mongod設定檔中的設定port
所以如果是打27018就是連入使用mongod2.conf這個設定檔的mongod

分別在三台電腦中輸入
>rs.initiate()
輸入完之後如果有顯示{“ok” : 1}表示設定成功
會看到你的輸入欄位由>變為rs0:OTHER>
過幾秒按一下Enter會發現rs0:OTHER>變為rs0>PRIMARY,此時可以知道這個mongod在Replica Set的地位已經是Primary

1.3. 替mongod加入新成員
在com6中輸入
rs.0:PRIMARY>rs.add(”192.168.1.1:27018”)
rs.0:PRIMARY>rs.add(”192.168.1.1:27019”)
顯示{“ok” : 1}表示新增成功

在com7中輸入
rs.0:PRIMARY>rs.add(”192.168.1.2:27018”)
rs.0:PRIMARY>rs.add(”192.168.1.2:27019”)
顯示{“ok” : 1}表示新增成功

在com8中輸入
rs.0:PRIMARY>rs.add(”192.168.1.3:27018”)
rs.0:PRIMARY>rs.add(”192.168.1.3:27019”)
顯示{“ok” : 1}表示新增成功

新增成功後可以輸入rs.status()查看Replica Set查看狀態

2. Sharding Cluster設定

2.1. 在com9中啟動config server與mongos
#mongod -f /etc/mongod-configserv.conf
#mongos -f /etc/mongod-routingserv.conf

2.2. 將Replica Set轉為Shard

2.2.1. 進入mongos
#mongo --port 27018

2.2.2. 將Replica Set加入Sharding Cluster中

輸入
mongos>sh.shardAdd("rs0/192.168.1.1:27017,192.168.1.1:27018,192.168.1.1:27019”)
回傳{ "shardAdded" : "rs0", "ok" : 1 }代表成功
mongos>sh.shardAdd("rs1/192.168.1.2:27017,192.168.1.2:27018,192.168.1.2:27019”)
回傳{ "shardAdded" : "rs1", "ok" : 1 }代表成功
mongos>sh.shardAdd("rs2/192.168.1.3:27017,192.168.1.3:27018,192.168.1.3:27019”)
回傳{ "shardAdded" : "rs2", "ok" : 1 }代表成功

新增完成後可以輸入
mongos>sh.status()
檢視sharding cluster狀態

做到這邊有三組ReplicaSet作為Shard的Sharding Cluster就建立成功了!!
(導入資料進入Sharding Cluster時,要導入到mongos中)

肆、測試成果

一、Replica Set測試

1. 檢查否有容錯轉移的功能,以rs0為例

關閉Primary的mongod
#mongod -f /etc/mongod1.conf --shutdown

連入使用原先為Primary的mongod
#mongo --port 27017

出現類似以下訊息表示已經成功關閉
exception: connect failed
此時在連上原先為Secondary的mongod
#mongo --port 27018
可以發現輸入指令的地方從原先的 rs0:SECONDARY> 變為 rs0:PRIMARY>

此時再重新啟動剛剛關閉的mongod
#mongod -f /etc/mongod1.conf

連上該mongod
#mongo --port 27017
可以發現輸入指令的地方從原先的 rs0:Primary> 變為 rs0:Secondary>

此時輸入
rs0:SECONDARY>rs.status()
可以發現使用port27018的mongod的"stateStr"已經由"SECONDARY"變為"PRIMARY"
而可以使用port27017的mongod的"stateStr"已經由"PRIMARY"變為"SECONDARY"

證明這個Replica Set具有容錯轉移功能

二、檢查資料是否會正常複製

1. 下載MongoDB所提供的資料及進行測試
#wget https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json

2. 將資料集導入mongod中

#mongoimport --port 27017 --db rstest --collection restaurants --file primer-dataset.json
(只能將資料導入PRIMARY中,在這個例子中,PRIMARY還是port27017這個mongod)

3. 進入PRIMARY
#mongo --port27017

4. 進入導入的資料庫
rs1:PRIMARY>use rstest

5. 查看資料表是否有導入成功
rs1:PRIMARY>show collections
有顯示restaurants表示導入成功

6. 查看資料表內容
rs1:PRIMARY>db.restaurants.find()

7.離開mongo shell
方法a: Ctrl+C
方法b: rs1:PRIMARY>exit

7. 看看其他Replica Set中的mongod是否有相同的資料
#mongo --port 27018

8. 查看資料庫
rs1:SECONDARY>show databases

此時會報錯
Error: error: { "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435 }

原因在此處有詳細的說明
請注意在預設的情況下,即使連結到 Secondary 節點也無法進行查詢的指令,必須透過 rs.slaveOk() 的指令才能宣告此一連線可用來進行查詢,此一設計的目的是為了避免程式誤連到 Secondary 後而不自知。因為 Secondary 節點與 Primary 節點內的資料存在一段的時間差,因此兩邊的資料可能不完全一致,這個現象對於某些應用來說將導致嚴重的不良後果,所以才需透過這種方式來確認程式的意圖。
9. 執行slaveOk
 rs1:SECONDARY>rslaveOk()

10. 此時就可以在Secondary執行與Primary相同的查詢

如果查詢發現結果與Primary相同,表示資料可以成功的自動複製

三、測試Sharding Cluster是否會正確切碎資料

1. 下載免費資料集並解壓縮
#wget http://jsonstudio.com/wp-content/uploads/2014/02/companies.zip
#unzip companies.zip

2. 導入資料到mongos(因為在sharding cluster中,mongos負責傳遞所有的請求)
#mongoimport --host 192.168.1.4 --port 27018 --db shtest --collection companies --file companies.json

3. 進入mongos並執行sh.status()
#mongo --port 27018
mongos>sh.status()
可以看到顯示的信息有這樣的東西
  databases:
        {  "_id" : "shtest",  "primary" : "rs2",  "partitioned" : false }

4. 讓shtest這個資料庫可以被切割
mongos>sh.enableSharding("shtest")
Enables sharding on the specified database. This does not automatically shard any collections but makes it possible to begin sharding collections using sh.shardCollection().
透過官方解釋可以得知,這個指令並不會讓資料庫裡面的資料表被切割,透過這個指令只是允許透過sh.shardCollection()來讓資料表可以開始被切割

5. 建立資料表索引
mongos>db.companies.ensureIndex({_id:"hashed"})
官方說明中寫道db.collection.ensureIndex()在3.0版後已經被較新的db.collection.createIndex()取代,但是使用較新的反而會無法切割成功,這點我還在研究

6. 開始切割
mongos>sh.shardCollection("shtest.companies", { "_id": "hashed" } )

7. 確認碎片狀態
mongos>sh.status()
可以看到以下訊息
 shtest.companies
                        shard key: { "_id" : "hashed" }
                        unique: false
                        balancing: true
                        chunks:
                                rs0     1
                                rs1     1
                                rs2     1
                        { "_id" : { "$minKey" : 1 } } -->> { "_id" : NumberLong("-591762798808544439") } on : rs0 Timestamp(2, 0)
                        { "_id" : NumberLong("-591762798808544439") } -->> { "_id" : NumberLong("7934911588406832349") } on : rs2 Timestamp(3, 0)
                        { "_id" : NumberLong("7934911588406832349") } -->> { "_id" : { "$maxKey" : 1 } } on : rs1 Timestamp(3, 1)
可以看出每一個Replica Set都已經獲得了一份切片,同時還會告訴用戶哪部分的切片在哪裡


伍、MongoDB相關指令


執行mongod
#mongod -f /etc/mongod.conf
關閉 mongod
#mongod -f /etc/mongod.conf --shutdown
執行config server
#mongod -f /etc/mongod-configserv.conf
關閉config server
#mongod -f /etc/mongod-configserv.conf --shutdown
執行 mongos
#mongos -f /etc/mongod-routingserv.conf
關閉 mongos instance
#killall mongos
導入資料表
#mongoimport --port port_number --db database_name --collection collection_name [--drop] --file file_name
(使用--drop的話,如果資料庫中有名稱相同的collection,會先將舊的collection刪除,在導入新的collection)
(導入CSV指定有點不同,可以去查閱官方文件)


陸、Q&A

問題一:

執行sh.addShard()時出現類似的錯誤
{        "ok" : 0,        "errmsg" : "in seed list rs0/192.168.1.1:27017,192.168.1.1:27018,192.168.1.1:27019, host 192.168.1.1:27017 does not belong to replica set rs0; found { hosts: [ \"com6:27017\", \"192.168.1.1:27018\", \"192.168.1.1:27019\" ], setName: \"rs0\", setVersion: 3, ismaster: true, secondary: false, primary: \"com6:27017\", me: \"com6:27017\", electionId: ObjectId('7fffffff0000000000000001'), maxBsonObjectSize: 16777216, maxMessageSizeBytes: 48000000, maxWriteBatchSize: 1000, localTime: new Date(1466274842539), maxWireVersion: 4, minWireVersion: 0, ok: 1.0 }",        "code" : 96}
解決辦法:修改rs0的hostname

1. 進入rs0的Primary並輸入rs.status(),檢查"members"的"name"
                {                        "_id" : 0,                        "name" : "com6:27017",                        "health" : 1,                        "state" : 1,                        "stateStr" : "PRIMARY",                        "uptime" : 103335,                        "optime" : {                                "ts" : Timestamp(1466175445, 12359),                                "t" : NumberLong(1)                        },                        "optimeDate" : ISODate("2016-06-17T14:57:25Z"),                        "electionTime" : Timestamp(1466171888, 2),                        "electionDate" : ISODate("2016-06-17T13:58:08Z"),                        "configVersion" : 3,                        "self" : true                }
如果是像這樣"name":"com6:27017",就要用rs.reconfig()修改"name"

2. 執行以下指令修改"name"
rs0:PRIMARY> cfg=rs.conf()
rs0:PRIMARY> cfg.members[0].host="192.168.1.1:27017"
rs0:PRIMARY> rs.reconfig(cfg)

此時會回傳{ "ok" : 1 }表示修改成功,可以輸入rs.status()檢查成果

此時sh.addShard()應該就可以正常執行

問題二:執行sh.addShard()時出現類似的錯誤

解決辦法一:檢查防火牆設定

解決辦法二:在mongos所在的電腦中的/etc/hosts
例如
192.168.1.1 com6
192.168.1.2 com7
192.168.1.3 com8


問題三:執行rs.initiate()時出現以下錯誤


{ "info2" : "no configuration explicitly specified -- making one", "me" : "0.0.0.0:27017", "errmsg" : "couldn't initiate : can't find self in the replset config", "ok" : 0}
解決辦法:輸入以下指令
>cfg = {"_id" : "rs0","members" : [{"_id" : 0,"host" : "192.168.1.1:27017"}]}
(Primary的資料)
>rs.initiate(cfg)

柒、免費資料集


1. https://raw.githubusercontent.com/mongodb/docs-assets/primer-dataset/primer-dataset.json

2. http://jsonstudio.com/wp-content/uploads/2014/02/companies.zip
(14.8M compressed)

3. http://jsonstudio.com/wp-content/uploads/2014/02/stocks.zip
(1.6M compressed)

4. http://jsonstudio.com/wp-content/uploads/2014/02/enron.zip
(3.9M compressed)

5. http://jsonstudio.com/wp-content/uploads/2014/02/zips.zip
(656K compressed).

6. http://jsonstudio.com/wp-content/uploads/2014/02/world_bank.zip

(436K compressed)

沒有留言:

張貼留言