有時候爲了考慮應用程序的性能或響應性,爲了提升讀取操做的吞吐率,一個常見的措施就是進行讀寫分離,MongoDB副本集對讀寫分離的支持是經過Read Preferences特性進行支持的,這個特性很是複雜和靈活。如下幾種應用場景可能會考慮對副本集進行讀寫分離:前端
1)操做不影響前端應用程序,好比備份或者報表;sql
2)在一個物理上分佈的副本集羣中,爲了減小應用程序的延遲,可能會優先選擇離應用程序更近的secondary節點而不是遠在千里以外機房的主節點;mongodb
3)故障發生時候可以提供一個優雅的降級。副本集primary節點宕機後再選出新的primary節點這段時間內(10秒或更長時間)可以依然響應客戶端應用的讀請求;shell
4)應用可以容忍必定程度的數據不一致性。數據庫
Read References:網絡
應用程序驅動經過read reference來設定如何對副本集進行讀取操做,默認的,客戶端驅動全部的讀操做都是直接訪問primary節點的,從而保證了數據的嚴格一致性。數據結構
但有時爲了緩解主節點的壓力,咱們可能須要直接從secondary節點讀取,只須要保證最終一致性就能夠了。併發
MongoDB 2.0以後支持五種的read preference模式:app
primary:默認,只從主節點上進行讀取操做;異步
primaryPreferred:在絕大部分的情形都是從主節點上讀取數據的,只有當主節點不可用的時候,好比在進行failover的10秒或更長的時間內會從secondary節點讀取數據。
警告:2.2版本以前的MongoDB對Read Preference支持的還不徹底,若是客戶端驅動採用primaryPreferred實際上讀取操做都會被路由到secondary節點。
secondary:只從secondary節點上進行讀取操做,存在的問題是secondary節點的數據會比primary節點數據「舊」。
secondaryPreferred:優先從secondary節點進行讀取操做;
nearest:既有可能從primary,也有可能從secondary節點讀取,這個決策是經過一個叫member selection過程處理的。
MongoDB容許在不一樣的粒度上指定這些模式:鏈接、數據庫、集合甚至單次的操做。不一樣語言的驅動基本都支持這些粒度。
oplog是一種特殊的capped collection,用來滾動的保存MongoDB中全部數據操做的日誌。副本集中secondary節點異步的從primary節點同步oplog而後從新執行它記錄的操做,以此達到了數據同步的做用。這就要求oplog必須是冪等的,也就是重複執行相同的oplog記錄獲得的數據結構必須是相同的。
事實上副本集中全部節點之間都相互進行heartbeat來維持聯繫,任何節點都能從其它節點複製oplog。
capped collection是MongoDB中一種提供高性能插入、讀取和刪除操做的固定大小集合。當集合被填滿的時候,新的插入的文檔會覆蓋老的文檔。由於oplog是capped collection因此指定它的大小很是重要。若是過小那麼老的文檔很快就被覆蓋了,那麼宕機的節點就很容易出現沒法同步數據的結果,但也不是越大越好,MongoDB在初始化副本集的時候都會有一個默認的oplog大小:
首先生產環境使用MongoDB毫無疑問必須的是64爲操做系統。其次大多數狀況下默認的大小是比較適合的。舉個例子,若是oplog大小爲空閒磁盤的5%,它在24H內能被填滿,也就是說secondary節點能夠中止複製oplog達24H後仍然可以catch up上primary節點。並且一般的MongoDB副本集的操做量要比這低得多。
oplog數據結構:
oplog的數據結構以下所示:
{ ts : ..., op: ..., ns: ..., o: ... o2: ... }
其中op有如下幾個值:
注:關於oplog有兩個常見的錯誤timestamp error和duplicate error,參看這裏:http://docs.mongodb.org/manual/tutorial/troubleshoot-replica-sets/#replica-set-troubleshooting-check-oplog-size
查看oplog大小:
經過db.printReplicationInfo() 能夠查看副本集節點的oplog狀態:
1. rs0:PRIMARY> db.printReplicationInfo() 2. configured oplog size: 1793.209765625MB 3. log length start to end: 12.643999999854714secs (0hrs) 4. oplog first event time: Sat Jan 17 1970 06:22:38 GMT+0800 (CST) 5. oplog last event time: Sat Jan 17 1970 06:22:51 GMT+0800 (CST) 6. now: Sat Aug 17 2013 18:02:12 GMT+0800 (CST)
以我以前搭建的副本集爲例,oplog的大小是1793MB,其中持有的數據時間區間只有12秒。
修改oplog大小:
能夠在啓動mongod的時候指定--oplogSize,單位MB:
7. ./bin/mongod --fork --dbpath data/rs0-0/ --logpath log/rs0-0/rs0-0.log --rest --replSet rs0 --oplogSize 500 --port 37017
但有的時候咱們可能須要修改現有副本集的oplog大小。這個本人很是不推薦,官網有詳細的教程,這裏我就不贅述了,能夠看這裏:http://docs.mongodb.org/manual/tutorial/change-oplog-size/。
在現有的副本集中修改oplog的大小是至關麻煩的並且影響副本集性能,所以咱們最好是預先根據應用的狀況評估好oplog的大小:若是應用程序是讀多寫少,那麼默認的大小已經足夠了。若是你的應用下面幾種場景不少可能考慮須要更大的oplog:
db.test.update({foo: "bar"}, {$set: {test: "success!"}}, false, true);
上面三點總結起來就是消耗了大量的oplog可是數據量卻沒有等量的增長。
數據滯後:
前面已經提到MongoDB副本集中secondary節點是經過oplog來同步primary節點數據的,那具體的細節是怎麼樣的?在說數據如何同步之間先介紹一下replication lag,由於存在數據同步那必然存在必定程度的落後。這個問題對於整個MongoDB副本集的部署是相當重要的。
1. rs0:PRIMARY> db.printSlaveReplicationInfo() 2. source: 192.168.129.129:37019 3. syncedTo: Thu Aug 15 2013 20:59:45 GMT+0800 (CST) 4. = 172971 secs ago (48.05hrs) 5. source: 192.168.129.129:37020 6. syncedTo: Thu Jan 01 1970 08:00:00 GMT+0800 (CST) 7. = 1376744556 secs ago (382429.04hrs)
當前集羣的情況是,37017端口是primary節點,37019和37020是secondary節點,其中37020已經宕機,能夠看到37019同步數據是在兩天前(由於這兩天我沒有對副本集有任何數據操做),而宕機的節點顯示的同步時間是一個很早時間點。
如今從新啓動37020後再執行命令:
1. rs0:PRIMARY> db.printSlaveReplicationInfo() 2. source: 192.168.129.129:37019 3. syncedTo: Thu Aug 15 2013 20:59:45 GMT+0800 (CST) 4. = 175566 secs ago (48.77hrs) 5. source: 192.168.129.129:37020 6. syncedTo: Thu Aug 15 2013 20:59:45 GMT+0800 (CST) 7. = 175566 secs ago (48.77hrs)
能夠看到兩個secondary節點的同步時間是一致的,咱們向集羣中插入幾條數據後再執行db.printSlaveReplicationInfo():
1. rs0:PRIMARY> db.test.insert({"name":"zhanjindong","age":23}) 2. rs0:PRIMARY> db.printSlaveReplicationInfo() 3. source: 192.168.129.129:37019 4. syncedTo: Sat Aug 17 2013 21:48:31 GMT+0800 (CST) 5. = 6 secs ago (0hrs) 6. source: 192.168.129.129:37020 7. syncedTo: Sat Aug 17 2013 21:48:31 GMT+0800 (CST) 8. = 6 secs ago (0hrs)
能夠看到很快就引起了primary和secondary之間的數據同步操做。
「滯後」是不可避免的,須要作的就是儘量減少這種滯後,主要涉及到如下幾點:
數據同步:
副本集中數據同步有兩個階段。
初始化(initial sync):這個過程發生在當副本集中建立一個新的數據庫或其中某個節點剛從宕機中恢復,或者向副本集中添加新的成員的時候,默認的,副本集中的節點會從離它最近的節點複製oplog來同步數據,這個最近的節點能夠是primary也能夠是擁有最新oplog副本的secondary節點。這能夠防止兩個secondary節點之間相互進行同步操做。
複製(replication):在初始化後這個操做會一直持續的進行着,以保持各個secondary節點之間的數據同步。
在MongoDB2.0之後的版本中,一旦初始化中肯定了一個同步的目標節點後,只有當和同步節點之間的鏈接斷開或鏈接過程當中產生異常纔可能會致使同步目標的變更,而且具備就近原則。考慮兩種場景:
在2.2版本之後,數據同步增長了一些額外的行爲:
注:buildIndexes指定副本集中成員是否能夠建立索引(某些狀況下好比沒有讀操做或者爲了提升寫性能能夠省略索引的建立)。固然即便該值爲false,MongoDB仍是能夠在_id上建立索引覺得複製操做服務。
從新數據同步:
有時當secondary節點落後太多沒法追遇上primary節點的時候,這時候可能須要考慮從新同步數據(Resync data)。
有兩種方法一種是指定一個空的目錄從新啓動落後的節點,這很簡單,可是數據量大的狀況下回花費很長的時間。另外一種方法是基於另外一個節點的數據做爲「種子」進行從新同步,關於這兩種方法在後面向一個現有副本集中添加成員一節會有詳細說明。
在如下幾種情景發生的時候,副本集經過「選舉」來決定副本集中的primary節點:
在一次選舉中包括hidden節點、仲裁者甚至正處於recovering狀態的節點都具備「投票權」。默認配置中全部參與選舉的節點具備相等的權利,固然在一些特定狀況下,應明確的指定某些secondary會優先成爲primary,好比一個遠在千里以外異地機房的節點就不該該成爲primary節點,選舉的權重經過設置priority來調節,默認該值都是1,在前面簡單副本集的搭建中已經介紹過了如何修改該值。
集羣中任何一個節點均可以否決選舉,即便它是non-voting member:
首先獲取最多選票的成員(實際上要超過半數)纔會成爲primary節點,這也說明了爲何當有兩個節點的集羣中primary節點宕機後,剩下的只能成爲secondary,當primary宕掉,此時副本集只剩下一個secondary,它只有1票,不超過總節點數的半數,它不會選舉本身爲primary。
要想更詳細的瞭解選舉細節,參看這篇源碼分析的文章:http://nosql-db.com/topic/514e6d9505c3fa4d47017da6
……
最近太忙,有時間再整理。