在上一篇文章《搭建高可用mongodb叢集(二)—— 副本集》 介紹了副本集的配置,這篇文章深入研究一下副本集的內部機制。還是帶著副本集的問題來看吧!
- 副本集故障轉移,主節點是如何選舉的?能否手動干涉下架某一臺主節點。
- 官方說副本集數量最好是奇數,為什麼?
- mongodb副本集是如何同步的?如果同步不及時會出現什麼情況?會不會出現不一致性?
- mongodb的故障轉移會不會無故自動發生?什麼條件會觸發?頻繁觸發可能會帶來系統負載加重?
Bully演算法 mongodb副本集故障轉移功能得益於它的選舉機制。選舉機制採用了Bully演算法,可以很方便從分散式節點中選出主節點。一個分散式叢集架構中一般都有一個所謂的主節點,可以有很多用途,比如快取機器節點後設資料,作為叢集的訪問入口等等。主節點有就有吧,我們幹嘛要什麼Bully演算法?要明白這個我們先看看這兩種架構:
- 指定主節點的架構,這種架構一般都會申明一個節點為主節點,其他節點都是從節點,如我們常用的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:
- get maxLocalOpOrdinal from each server.
- if a majority of servers are not up (from this server’s POV), remain in Secondary mode and stop.
- if the last op time seems very old, stop and await human intervention.
- else, using a consensus protocol, pick the server with the highest maxLocalOpOrdinal as the Primary.
大致翻譯過來為使用一致協議選擇主節點。基本步驟為:
- 得到每個伺服器節點的最後操作時間戳。每個mongodb都有oplog機制會記錄本機的操作,方便和主伺服器進行對比資料是否同步還可以用於錯誤恢復。
- 如果叢集中大部分伺服器down機了,保留活著的節點都為 secondary狀態並停止,不選舉了。
- 如果叢集中選舉出來的主節點或者所有從節點最後一次同步時間看起來很舊了,停止選舉等待人來操作。
- 如果上面都沒有問題就選擇最後操作時間戳最新(保證資料是最新的)的伺服器節點作為主節點。
這裡提到了一個一致協議(其實就是bully演算法),這個和資料庫的一致性協議還是有些區別,一致協議主要強調的是通過一些機制保證大家達成共識;而一致性協議強調的是操作的順序一致性,比如同時讀寫一個資料會不會出現髒資料。一致協議在分散式裡有一個經典的演算法叫“Paxos演算法”,後續再介紹。
上面有個問題,就是所有從節點的最後操作時間都是一樣怎麼辦?就是誰先成為主節點的時間最快就選誰。
選舉觸發條件 選舉不是什麼時刻都會被觸發的,有以下情況可以觸發。
- 初始化一個副本集時。
- 副本集和主節點斷開連線,可能是網路問題。
- 主節點掛掉。
選舉還有個前提條件,參與選舉的節點數量必須大於副本集總節點數量的一半,如果已經小於一半了所有節點保持只讀狀態。
日誌將會出現:
1 |
can't see a majority of the set, relinquishing primary |
主節點掛掉能否人為干預?答案是肯定的。
- 可以通過replSetStepDown命令下架主節點。這個命令可以登入主節點使用
1 |
db.adminCommand({replSetStepDown : 1}) |
如果殺不掉可以使用強制開關
1 |
db.adminCommand({replSetStepDown : 1, force : true}) |
- 或者使用 rs.stepDown(120)也可以達到同樣的效果,中間的數字指不能在停止服務這段時間成為主節點,單位為秒。
- 設定一個從節點有比主節點有更高的優先順序。
先檢視當前叢集中優先順序,通過rs.conf()命令,預設優先順序為1是不顯示的,這裡標示出來。
1 |
rs.conf(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "_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的主機可以優先成為主節點。
1 2 3 4 5 |
cfg = rs.conf() cfg.members[0].priority = 1 cfg.members[1].priority = 2 cfg.members[2].priority = 1 rs.reconfig(cfg) |
然後再執行rs.conf()命令檢視優先順序已經設定成功,主節點選舉也會觸發。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "_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型別。 - 當主節點不能和大部分從節點通訊。把主機節點網線拔掉,嘿嘿:)優先順序還可以這麼用,如果我們不想設定什麼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複製指初始化同步過後,節點之間的實時同步一般是增量同步。初始化同步不只是在第一次才會被處罰,有以下兩種情況會觸發:
- secondary第一次加入,這個是肯定的。
- 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複製資料。
設定同步還要注意以下幾點:
- secondary不會從delayed和hidden成員上覆制資料。
- 只要是需要同步,兩個成員的buildindexes必須要相同無論是否是true和false。buildindexes主要用來設定是否這個節點的資料用於查詢,預設為true。
- 如果同步操作30秒都沒有反應,則會重新選擇一個節點進行同步。
到此,本章前面提到的問題全部解決了,不得不說mongodb的設計還真是強大!
後續繼續解決上一節這幾個問題:
- 主節點掛了能否自動切換連線?目前需要手工切換。
- 主節點的讀寫壓力過大如何解決?
還有這兩個問題後續解決:
- 從節點每個上面的資料都是對資料庫全量拷貝,從節點壓力會不會過大?
- 資料壓力大到機器支撐不了的時候能否做到自動擴充套件?