MongoDB副本集概述
以下圖片摘自MongoDB官方文檔:http://docs.mongodb.org/manual/core/replication-introduction/

Primary節點接收客戶端所有的寫操作,整個副本集只會有一個primary節點。MongoDB副本集提供嚴格的一致性。主節點將所有的操作寫入一個叫oplog的capped collection(這個collection的大小一般為磁盤剩余空間的5%,不同的系統可能不一樣,詳見http://docs.mongodb.org/manual/core/replica-set-oplog/)中,secondary節點通過復制oplog並執行oplog中的所有操作,因為對oplog的執行是冪等的,所以secondary節點上的數據可以保持和primary節點一樣,當然這有一個“追趕”(catch up)的過程,會存在一定的落后(Lag)有時候因為網絡延遲或宕機導致從節點永遠趕不上主節點,這時候需要采取人為的干預了(后面會說到Resyncing Member of Replica Set)。

默認所有的讀操作也是走的primary節點,當然客戶端可以選擇從secondary節點進行讀取操作以減小主節點的壓力(后面會對讀寫分離有詳細說明)。
各個節點之間是通過心跳機制來維持聯系的,當主節點無法和集群中其他節點通信超過10秒,集群會從剩下的節點中選擇一個secondary作為primary,這個過程叫做選舉(election),每個secondary節點都有一個優先級priority來參與投票(也可以有沒有投票權的secondary節點),priority值越大就越優先成為主節點(所有的節點可以有相同的優先級,默認值都是1)。election的策略不僅僅就是根據priority值來,會綜合很多其他的因素。總之MongoDB通過heartbeat和election機制實現了自動的Failover:

副本集要求參與選舉投票(vote)的節點數為奇數,這很容易理解。當我們實際環境中因為機器等原因限制只有兩個(或偶數)的節點,這時為了實現Automatic Failover引入另一類節點:仲裁者(arbiter),仲裁者只參與投票不擁有實際的數據,因此它對物理資源要求不嚴格。

上面已經提到了primary,secondary和arbiter,整個MongoDB副本集群中除了這三種類型的節點還有其他幾種:
- Secondary-Only:這種類型的節點和secondary節點一樣擁有數據副本,但是它們在任何情形下都成為不了primary節點。
- Hidden:這種類型的節點對客戶端程序來說是不可見的,同樣也不能成為primary節點,但是Hidden成員能夠參與選舉投票。
- Delayed:這種類型的成員通過人為的設置,可以指定一個時間來延遲從primary節點同步數據。Delayed成員的作用在於幫助集群從一些誤操作中恢復,比如管理員誤刪除了某個集合。不至於迅速擴散到整個集群中。因此Delayed節點必須不能成為primary節點(priority為0)並且是Hidden的。
- Non-Voting:這就是上面提到了沒有選舉權的secondary節點。這種類型的節點一般當集群節點數超過12才會需要。
簡單副本集的搭建
官方建議的最小化的副本集為Three Member Sets,一個primary和兩個secondary。我們先就搭建一個這樣的測試環境。

首先建立三個數據目錄和日志目錄:
1. cd /usr/local/mongodb-2.4.1/data/ 2. mkdir -p rs0-0 rs0-1 rs0-2 3. cd /usr/local/mongodb-2.4.1/log/ 4. mkdir -p rs0-0 rs0-1 rs0-2
然后我們以守護進程的方式啟動三個mongod進程,端口分別是37017,37018和37019:
1. ./bin/mongod --fork --dbpath data/rs0-0/ --logpath log/rs0-0/rs0-0.log --rest --replSet rs0 --port 37017 2. ./bin/mongod --fork --dbpath data/rs0-1/ --logpath log/rs0-1/rs0-1.log --rest --replSet rs0 --port 37018 3. ./bin/mongod --fork --dbpath data/rs0-2/ --logpath log/rs0-2/rs0-2.log --rest --replSet rs0 --port 37019
跟啟普通的mongod進程基本相同,不同的跟了--replSet選項,rs0是該副本集的名稱。--rest參數是打開web監控頁面,比如我們這里監聽37017端口,則打開http://192.168.129.129:38017/(mongod端口加上1000)就可以看到這個mongodb數據庫進程的信息,如果是副本集就能查看整個副本集的相關信息。
然后我們用mongo shell連上端口為37017的mongod:
1. ./bin/mongo -port 37017 2. use admin
接着我們需要初始化一個Replica Set:首先創建一個副本集配置對象:
1. rsconf={ 2. "_id" : "rs0", 3. "members" : [ 4. { 5. "_id" : 0, 6. "host" : "192.168.129.129:37017" 7. } 8. ] 9. }
然后用rs.initiate()進程初始化:
1. rs.initiate(rsconf) 2. { 3. "info" : "Config now saved locally. Should come online in about a minute.", 4. "ok" : 1 5. }
添加成員:
通過rs.add()將另外兩個mongod添加到副本集當中:
1. rs0:PRIMARY> rs.add("192.168.129.129:37018") 2. { "ok" : 1 } 3. rs0:PRIMARY> rs.add("192.168.129.129:37019") 4. { "ok" : 1 }
會發現37017這個mongod默認就是PRIMARY節點了。通過rs.conf()可以查看集群的配置情況:
1. rs0:PRIMARY> rs.conf() 2. { 3. "_id" : "rs0", 4. "version" : 3, 5. "members" : [ 6. { 7. "_id" : 0, 8. "host" : "192.168.129.129:37017" 9. }, 10. { 11. "_id" : 1, 12. "host" : "192.168.129.129:37018" 13. }, 14. { 15. "_id" : 2, 16. "host" : "192.168.129.129:37019" 17. } 18. ] 19. }
修改priority:
副本中所有的secondary節點都有一個priority值,為任意的浮點數,該值越大則該節點在election中越優先成為primary節點,通過下面的命令修改該值,目前primary節點是37017:
1. rs0:PRIMARY> cfg=rs.conf() 2. { 3. "_id" : "rs0", 4. "version" : 7, 5. "members" : [ 6. { 7. "_id" : 0, 8. "host" : "192.168.129.129:37017" 9. }, 10. { 11. "_id" : 1, 12. "host" : "192.168.129.129:37018" 13. }, 14. { 15. "_id" : 2, 16. "host" : "192.168.129.129:37019" 17. } 18. ] 19. }
我們將37019節點的priority設置成2:
1. rs0:PRIMARY> cfg=rs.conf() 2. cfg.members[2].priority = 2 3. 2
這里數組的索引2其實跟rs.conf查看到的每個成員的_id不是一回事。
然后執行:
1. rs0:PRIMARY> rs.reconfig(cfg)
注意:執行rs.reconfig()命令會強制整個副本集集群進行一次election,這樣priority較高的37019節點便成了primary節點:

整個election過程需要一點時間,在這之間整個集群的所有節點都是secondary。
添加仲裁者:
首先需要啟動一個作為arbiter的mongod進程,端口40000,雖然arbiter不持有數據但是仍然需要數據目錄來保存一些配置信息:
1. mkdir –p data/rs0-arb 2. mkdir –p log/rs0-arb 3. ./bin/mongod --fork --dbpath data/rs0-arb/ --logpath log/rs0-arb/rs0-arb.log --rest --replSet rs0 --port 40000
然后進入primary節點執行下面命令添加arbiter:
1. rs0:PRIMARY> rs.addArb("192.168.129.129:40000") 2. { "ok" : 1 } 3. rs0:PRIMARY> rs.conf() 4. { 5. "_id" : "rs0", 6. "version" : 6, 7. "members" : [ 8. { 9. "_id" : 0, 10. "host" : "192.168.129.129:37017" 11. }, 12. { 13. "_id" : 1, 14. "host" : "192.168.129.129:37018" 15. }, 16. { 17. "_id" : 2, 18. "host" : "192.168.129.129:37019" 19. }, 20. { 21. "_id" : 3, 22. "host" : "192.168.129.129:40000", 23. "arbiterOnly" : true 24. } 25. ] 26. }
仲裁節點的作用:
通過實際測試發現,當整個副本集集群中達到50%的節點(包括仲裁節點)不可用的時候,剩下的節點只能成為secondary節點,整個集群只能讀不能寫。比如集群中有1個primary節點,2個secondary節點,加1個arbit節點時:當兩個secondary節點掛掉了,那么剩下的原來的primary節點也只能降級為secondary節點;當集群中有1個primary節點,1個secondary節點和1個arbit節點,這時即使primary節點掛了,剩下的secondary節點也會自動成為primary節點。因為仲裁節點不復制數據,因此利用仲裁節點可以實現最少的機器開銷達到兩個節點熱備的效果。
移除成員:
移除一個成員使用rs.remove()命令:
1. rs0:PRIMARY> rs.remove("192.168.129.129:37019") 2. Sun Aug 11 12:19:22.754 DBClientCursor::init call() failed 3. Sun Aug 11 12:19:22.874 JavaScript execution failed: Error: error doing query: failed at src/mongo/shell/query.js:L78 4. Sun Aug 11 12:19:22.909 trying reconnect to 127.0.0.1:37017 5. Sun Aug 11 12:19:22.909 reconnect 127.0.0.1:37017 ok
需要注意的是:雖然有錯誤信息,但其實操作已經成功了。參看官方的文檔:

每改變一次集群的配置,副本集的version都會加1。我們重新將37019加入rs0這次提示信息有點不一樣:
1. rs0:PRIMARY> rs.add("192.168.129.129:37019") 2. { "down" : [ "192.168.129.129:37019" ], "ok" : 1 }
我們打開http://192.168.129.129:38017/可以看到整個副本集的相關信息:

至此一個簡單的用於開發和測試Three Member Sets就搭建完成了。下節會在此基礎上做一些簡單的數據測試。
