1. 程式人生 > >mongodb副本集的內部機制(借鑒lanceyan.com)

mongodb副本集的內部機制(借鑒lanceyan.com)

proc 導致 功能 來看 href 開始 既然 不想 for

針對mongodb的內部機制提出以下幾個引導性的問題:

  • 副本集故障轉移,主節點是如何選舉的?能否手動幹涉下架某一臺主節點。
  • 官方說副本集數量最好是奇數,為什麽?
  • mongodb副本集是如何同步的?如果同步不及時會出現什麽情況?會不會出現不一致性?
  • mongodb的故障轉移會不會無故自動發生?什麽條件會觸發?頻繁觸發可能會帶來系統負載加重?

Bully算法 mongodb副本集故障轉移功能得益於它的選舉機制。選舉機制采用了Bully算法,可以很方便從分布式節點中選出主節點。一個分布式集群架構中一般都有一個所謂的主節點,可以有很多用途,比如緩存機器節點元數據,作為集群的訪問入口等等。主節點有就有吧,我們幹嘛要什麽Bully算法?要明白這個我們先看看這兩種架構:

  1. 指定主節點的架構,這種架構一般都會申明一個節點為主節點,其他節點都是從節點,如我們常用的mysql就是這樣。但是這樣架構我們在第一節說了整個集群如果主節點掛掉了就得手工操作,上架一個新的主節點或者從從節點恢復數據,不太靈活。

    技術分享

  2. 不指定主節點,集群中的任意節點都可以成為主節點。mongodb也就是采用這種架構,一但主節點掛了其他從節點自動接替變成主節點。如下圖:

    技術分享

好了,問題就在這個地方,既然所有節點都是一樣,一但主節點掛了,怎麽選擇出來下一個節點是誰來做為主節點呢?這就是Bully算法解決的問題。

那什麽是Bully算法,Bully算法是一種協調者(主節點)競選算法,主要思想是集群的每個成員都可以聲明它是主節點並通知其他節點。別的節點可以選擇接受這個聲稱或是拒絕並進入主節點競爭。被其他所有節點接受的節點才能成為主節點。節點按照一些屬性來判斷誰應該勝出。這個屬性可以是一個靜態ID,也可以是更新的度量像最近一次事務ID(最新的節點會勝出)。詳情請參考NoSQL數據庫分布式算法的協調者競選還有維基百科的解釋 。

選舉 那mongodb是怎進行選舉的呢?官方這麽描述:

We use a consensus protocol to pick a primary. Exact details will be spared here but that basic process is:

  1. get maxLocalOpOrdinal from each server.
  2. if a majority of servers are not up (from this server’s POV), remain in Secondary mode and stop.
  3. if the last op time seems very old, stop and await human intervention.
  4. else, using a consensus protocol, pick the server with the highest maxLocalOpOrdinal as the Primary.

大致翻譯過來為使用一致協議選擇主節點。基本步驟為:

  1. 得到每個服務器節點的最後操作時間戳。每個mongodb都有oplog機制會記錄本機的操作,方便和主服務器進行對比數據是否同步還可以用於錯誤恢復。
  2. 如果集群中大部分服務器down機了,保留活著的節點都為 secondary狀態並停止,不選舉了。
  3. 如果集群中選舉出來的主節點或者所有從節點最後一次同步時間看起來很舊了,停止選舉等待人來操作。
  4. 如果上面都沒有問題就選擇最後操作時間戳最新(保證數據是最新的)的服務器節點作為主節點。

這裏提到了一個一致協議(其實就是bully算法),這個和數據庫的一致性協議還是有些區別,一致協議主要強調的是通過一些機制保證大家達成共識;而一致性協議強調的是操作的順序一致性,比如同時讀寫一個數據會不會出現臟數據。一致協議在分布式裏有一個經典的算法叫“Paxos算法”,後續再介紹。

上面有個問題,就是所有從節點的最後操作時間都是一樣怎麽辦?就是誰先成為主節點的時間最快就選誰。

選舉觸發條件 選舉不是什麽時刻都會被觸發的,有以下情況可以觸發。

  1. 初始化一個副本集時。
  2. 副本集和主節點斷開連接,可能是網絡問題。
  3. 主節點掛掉。

選舉還有個前提條件,參與選舉的節點數量必須大於副本集總節點數量的一半,如果已經小於一半了所有節點保持只讀狀態。
日誌將會出現:

can‘t see a majority of the set, relinquishing primary

主節點掛掉能否人為幹預?答案是肯定的。

  1. 可以通過replSetStepDown命令下架主節點。這個命令可以登錄主節點使用 db.adminCommand({replSetStepDown : 1})

    如果殺不掉可以使用強制開關

    db.adminCommand({replSetStepDown : 1, force : true})

    或者使用 rs.stepDown(120)也可以達到同樣的效果,中間的數字指不能在停止服務這段時間成為主節點,單位為秒。

  2. 設置一個從節點有比主節點有更高的優先級。
    先查看當前集群中優先級,通過rs.conf()命令,默認優先級為1是不顯示的,這裏標示出來。

    rs.conf(); {
    "_id" : "rs0",
    "version" : 9,
    "members" : [
    {
    "_id" : 0,
    "host" : "192.168.1.136:27017" },
    {
    "_id" : 1,
    "host" : "192.168.1.137:27017" },
    {
    "_id" : 2,
    "host" : "192.168.1.138:27017" }
    ]
    }

    我們來設置,讓id為1的主機可以優先成為主節點。

    cfg = rs.conf()
    cfg.members[0].priority = 1
    cfg.members[1].priority = 2
    cfg.members[2].priority = 1
    rs.reconfig(cfg)

    然後再執行rs.conf()命令查看優先級已經設置成功,主節點選舉也會觸發。

    {
    "_id" : "rs0",
    "version" : 9,
    "members" : [
    {
    "_id" : 0,
    "host" : "192.168.1.136:27017" },
    {
    "_id" : 1,
    "host" : "192.168.1.137:27017",
    "priority" : 2
    },
    {
    "_id" : 2,
    "host" : "192.168.1.138:27017" }
    ]
    }

    如果不想讓一個從節點成為主節點可以怎麽操作?
    a、使用rs.freeze(120)凍結指定的秒數不能選舉成為主節點。
    b、按照上一篇設置節點為Non-Voting類型。

  3. 當主節點不能和大部分從節點通訊。把主機節點網線拔掉,嘿嘿:)

    優先級還可以這麽用,如果我們不想設置什麽hidden節點,就用secondary類型作為備份節點也不想讓他成為主節點怎麽辦?看下圖,共三個節點分布在兩個數據中心,數據中心2的節點設置優先級為0不能成為主節點,但是可以參與選舉、數據復制。架構還是很靈活吧!

    技術分享

奇數 官方推薦副本集的成員數量為奇數,最多12個副本集節點,最多7個節點參與選舉。最多12個副本集節點是因為沒必要一份數據復制那麽多份,備份太多反而增加了網絡負載和拖慢了集群性能;而最多7個節點參與選舉是因為內部選舉機制節點數量太多就會導致1分鐘內還選不出主節點,凡事只要適當就好。這個“12”、“7”數字還好,通過他們官方經過性能測試定義出來可以理解。具體還有哪些限制參考官方文檔《 MongoDB Limits and Thresholds 》。 但是這裏一直沒搞懂整個集群為什麽要奇數,通過測試集群的數量為偶數也是可以運行的,參考這個文章http://www.itpub.net/thread-1740982-1-1.html。後來突然看了一篇stackoverflow的文章終於頓悟了,mongodb本身設計的就是一個可以跨IDC的分布式數據庫,所以我們應該把它放到大的環境來看。

假設四個節點被分成兩個IDC,每個IDC各兩臺機器,如下圖。但這樣就出現了個問題,如果兩個IDC網絡斷掉,這在廣域網上很容易出現的問題,在上面選舉中提到只要主節點和集群中大部分節點斷開鏈接就會開始一輪新的選舉操作,不過mongodb副本集兩邊都只有兩個節點,但是選舉要求參與的節點數量必須大於一半,這樣所有集群節點都沒辦法參與選舉,只會處於只讀狀態。但是如果是奇數節點就不會出現這個問題,假設3個節點,只要有2個節點活著就可以選舉,5個中的3個,7個中的4個。。。

技術分享

心跳 綜上所述,整個集群需要保持一定的通信才能知道哪些節點活著哪些節點掛掉。mongodb節點會向副本集中的其他節點每兩秒就會發送一次pings包,如果其他節點在10秒鐘之內沒有返回就標示為不能訪問。每個節點內部都會維護一個狀態映射表,表明當前每個節點是什麽角色、日誌時間戳等關鍵信息。如果是主節點,除了維護映射表外還需要檢查自己能否和集群中內大部分節點通訊,如果不能則把自己降級為secondary只讀節點。

同步,副本集同步分為初始化同步和keep復制。初始化同步指全量從主節點同步數據,如果主節點數據量比較大同步時間會比較長。而keep復制指初始化同步過後,節點之間的實時同步一般是增量同步。初始化同步不只是在第一次才會被處罰,有以下兩種情況會觸發:

  1. secondary第一次加入,這個是肯定的。
  2. secondary落後的數據量超過了oplog的大小,這樣也會被全量復制。

那什麽是oplog的大小?前面說過oplog保存了數據的操作記錄,secondary復制oplog並把裏面的操作在secondary執行一遍。但是oplog也是mongodb的一個集合,保存在local.oplog.rs裏,但是這個oplog是一個capped collection也就是固定大小的集合,新數據加入超過集合的大小會覆蓋。所以這裏需要註意,跨IDC的復制要設置合適的oplogSize,避免在生產環境經常產生全量復制。oplogSize 可以通過–oplogSize設置大小,對於linux 和windows 64位,oplog size默認為剩余磁盤空間的5%。

同步也並非只能從主節點同步,假設集群中3個節點,節點1是主節點在IDC1,節點2、節點3在IDC2,初始化節點2、節點3會從節點1同步數據。後面節點2、節點3會使用就近原則從當前IDC的副本集中進行復制,只要有一個節點從IDC1的節點1復制數據。

設置同步還要註意以下幾點:

    1. secondary不會從delayed和hidden成員上復制數據。
    2. 只要是需要同步,兩個成員的buildindexes必須要相同無論是否是true和false。buildindexes主要用來設置是否這個節點的數據用於查詢,默認為true。
    3. 如果同步操作30秒都沒有反應,則會重新選擇一個節點進行同步。

mongodb副本集的內部機制(借鑒lanceyan.com)