MongoDB副本集學習(三):性能和優化相關

Read Preferences/讀寫分離

有時候爲了考慮應用程序的性能或響應性,爲了提升讀取操做的吞吐率,一個常見的措施就是進行讀寫分離,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

oplog是一種特殊的capped collection,用來滾動的保存MongoDB中全部數據操做的日誌。副本集中secondary節點異步的從primary節點同步oplog而後從新執行它記錄的操做,以此達到了數據同步的做用。這就要求oplog必須是冪等的,也就是重複執行相同的oplog記錄獲得的數據結構必須是相同的。

事實上副本集中全部節點之間都相互進行heartbeat來維持聯繫,任何節點都能從其它節點複製oplog。

capped collection是MongoDB中一種提供高性能插入、讀取和刪除操做的固定大小集合。當集合被填滿的時候,新的插入的文檔會覆蓋老的文檔。由於oplog是capped collection因此指定它的大小很是重要。若是過小那麼老的文檔很快就被覆蓋了,那麼宕機的節點就很容易出現沒法同步數據的結果,但也不是越大越好,MongoDB在初始化副本集的時候都會有一個默認的oplog大小:

  • 在64位的Linux,Solaris,FreeBSD以及Windows系統上,MongoDB會分配磁盤剩餘空間的5%做爲oplog的大小,若是這部分小於1GB則分配1GB的空間。
  • 在64的OS X系統上會分配183MB。
  • 在32位的系統上則只分配48MB。

首先生產環境使用MongoDB毫無疑問必須的是64爲操做系統。其次大多數狀況下默認的大小是比較適合的。舉個例子,若是oplog大小爲空閒磁盤的5%,它在24H內能被填滿,也就是說secondary節點能夠中止複製oplog達24H後仍然可以catch up上primary節點。並且一般的MongoDB副本集的操做量要比這低得多。

 

oplog數據結構

oplog的數據結構以下所示:

{ ts : ..., op: ..., ns: ..., o: ... o2: ...  }

  • ts: 8字節的時間戳,由4字節unix timestamp + 4字節自增計數表示。這個值很重要,在選舉(如master宕機時)新primary時,會選擇ts最大的那個secondary做爲新primary。
  • op:1字節的操做類型,例如i表示insert,d表示delete。
  • ns:操做所在的namespace。
  • o:操做所對應的document,即當前操做的內容(好比更新操做時要更新的的字段和值)
  • o2: 在執行更新操做時的where條件,僅限於update時纔有該屬性

其中op有如下幾個值:

  • "i": insert
  • "u": update
  • "d": delete
  • "c": db cmd
  • "db":聲明當前數據庫 (其中ns 被設置成爲=>數據庫名稱+ '.')
  • "n":  no 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:

  • 在同一個時刻更新多個文檔:oplog爲了維持冪等性必須將mutil-updates翻譯成一個個獨立的操做,這會用去大量的oplog空間,但數據庫中的數據量卻沒有相對稱的增長。
  • 多文檔同時更新從1.1.3就有的特性,在mongo shell執行相似以下的命令,第四個參數必須制定爲true:
  • db.test.update({foo: "bar"}, {$set: {test: "success!"}}, false, true);
  • 在插入時同時刪除相同大小數據:和上面的結果同樣在數據量沒有增長的狀況下卻消耗了大量的oplog空間。
  • 大量的In-Place更新操做:In-Place更新是指更新文檔中原有的部分,但並不增長文檔的大小。

上面三點總結起來就是消耗了大量的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之間的數據同步操做。

「滯後」是不可避免的,須要作的就是儘量減少這種滯後,主要涉及到如下幾點:

  • 網絡延遲:這是全部分佈式系統都存在的問題。咱們能作的就是儘量減少副本集節點之間的網絡延遲。
  • 磁盤吞吐量:secondary節點上數據刷入磁盤的速度比primary節點上慢的話會致使secondary節點很難跟上primary節點的節奏。
  • 併發:併發大的狀況下,primary節點上的一些耗時操做會阻塞secondary節點的複製操做,致使複製操做跟不上主節點的寫入負荷。解決方法是經過設置操做的write concern(參看這裏:http://docs.mongodb.org/manual/core/write-concern/#replica-set-write-concern)默認的副本集中寫入操做只關心primary節點,可是能夠指定寫入操做同時傳播到其餘secondary節點,代價就是嚴重影響集羣的併發性。
    • 注意:並且這裏還存在一個問題若是,若是寫入操做關心的某個節點宕機了,那麼操做將會一直被阻塞直到節點恢復。
  • 適當的write concern:咱們爲了提升集羣寫操做的吞吐量常常會將writer concern設置爲unacknowledged write concern,這致使primary節點的寫操做很快而secondary節點複製操做跟不上。解決方法和第三點是相似的就是在性能和一致性之間作權衡。

數據同步:

副本集中數據同步有兩個階段。

初始化(initial sync):這個過程發生在當副本集中建立一個新的數據庫或其中某個節點剛從宕機中恢復,或者向副本集中添加新的成員的時候,默認的,副本集中的節點會從離它最近的節點複製oplog來同步數據,這個最近的節點能夠是primary也能夠是擁有最新oplog副本的secondary節點。這能夠防止兩個secondary節點之間相互進行同步操做。

複製(replication):在初始化後這個操做會一直持續的進行着,以保持各個secondary節點之間的數據同步。

在MongoDB2.0之後的版本中,一旦初始化中肯定了一個同步的目標節點後,只有當和同步節點之間的鏈接斷開或鏈接過程當中產生異常纔可能會致使同步目標的變更,而且具備就近原則。考慮兩種場景:

  • 1) 有兩個secondary節點在一個機房,primary在另一個機房。假設幾乎在同一時間啓動這三個實例(以前都沒有數據和oplog),那麼兩個secondary節點應該都是從primary節點同步數據,由於他們以前見都不會擁有比對方更新的oplog。若是重啓其中一個secondary,那麼它的同步目標將會變成另外一個secondary,由於就近原則。
  • 2) 若是有一個primary和一個secondary分別在不一樣的機房,那麼在以前secondary所在的機房中向副本集中新加一個節點時,那麼新節點必然是從原先的那個secondary節點同步數據的。

在2.2版本之後,數據同步增長了一些額外的行爲:

  • 1) secondary節點只有當集羣中沒有其餘選擇的時候纔會從delayed節點同步數據;
  • 2) secondary節點毫不會從hidden節點同步數據;
  • 3) 當一個節點新加入副本集中會有一個recovering過程,在這段時間內secondary不會進行數據同步操做;
  • 4) 當一個節點從另外一個節點同步數據的時候,須要保證兩個節點的local.system.replset.members[n].buildIndexes值是同樣的,要不都是false,要不都是true。

:buildIndexes指定副本集中成員是否能夠建立索引(某些狀況下好比沒有讀操做或者爲了提升寫性能能夠省略索引的建立)。固然即便該值爲false,MongoDB仍是能夠在_id上建立索引覺得複製操做服務。

從新數據同步

有時當secondary節點落後太多沒法追遇上primary節點的時候,這時候可能須要考慮從新同步數據(Resync data)。

有兩種方法一種是指定一個空的目錄從新啓動落後的節點,這很簡單,可是數據量大的狀況下回花費很長的時間。另外一種方法是基於另外一個節點的數據做爲「種子」進行從新同步,關於這兩種方法在後面向一個現有副本集中添加成員一節會有詳細說明。

 

Elction

在如下幾種情景發生的時候,副本集經過「選舉」來決定副本集中的primary節點:

  • 當第一次初始化一個副本集的時候;
  • primary幾點steps down的時候,出現這種狀況多是由於執行了replSetStepDown命令,或者是集羣中出現了更適合當primary的節點,好比當primary節點和集羣中其餘大部分節點沒法通訊的時候,當primary steps down的時候,它會關閉全部客戶端的鏈接。
  • 當集羣中一個secondary節點沒法和primary節點創建鏈接的時候也會致使一次election發生。
  • 一次failover。
  • 執行rs.conf()命令。

 

在一次選舉中包括hidden節點、仲裁者甚至正處於recovering狀態的節點都具備「投票權」。默認配置中全部參與選舉的節點具備相等的權利,固然在一些特定狀況下,應明確的指定某些secondary會優先成爲primary,好比一個遠在千里以外異地機房的節點就不該該成爲primary節點,選舉的權重經過設置priority來調節,默認該值都是1,在前面簡單副本集的搭建中已經介紹過了如何修改該值。

集羣中任何一個節點均可以否決選舉,即便它是non-voting member:

  • 若是發起選舉的節點不具備選舉權(priority爲0的成員);
  • 發起選舉的節點數據落後太多;
  • 發起選舉的節點的priority值比集羣中其餘某一個節點的小;
  • 若是當前的primary節點比發起選舉的節點擁有更新或同等新的數據(也就「optime」值相等或更大)。
  • 當前的primary節點會否決,若是它擁有比發起選舉的節點更新或相同新的數據。

首先獲取最多選票的成員(實際上要超過半數)纔會成爲primary節點,這也說明了爲何當有兩個節點的集羣中primary節點宕機後,剩下的只能成爲secondary,當primary宕掉,此時副本集只剩下一個secondary,它只有1票,不超過總節點數的半數,它不會選舉本身爲primary。

 

要想更詳細的瞭解選舉細節,參看這篇源碼分析的文章:http://nosql-db.com/topic/514e6d9505c3fa4d47017da6

 

索引

……

 

最近太忙,有時間再整理。

相關文章
相關標籤/搜索