MongoDB公司原名10gen,創立於2007年,在2013年收到一筆2.31億美元的融資後,公司市值評估已增至10億美元級別,這個高度是知名開源公司Red Hat(創建於1993年)20年的奮鬥成果。
高性能、易擴展一直是MongoDB的立足之本,同時規範的文檔和接口更讓其深受用戶喜愛,這一點從分析DB-Engines的得分結果不難看出——僅僅1年時間,MongoDB就完成了第7名到第五名的提升,得分就從124分上升至214分,上升值是第四名PotgreSQL的兩倍,同時當下與PostgreSQL的得分也只相差16分不到。
點擊查看大圖
MongoDB能以如此速度發展,很大程度上歸結於許多傳統關係數據庫已無法應對當下數據處理的擴展性需求,雖然它們久經考驗,並具備不錯的性能及穩定性。然而區別於以往的使用方法,許多NoSQL都有着自己的限制,從而也導致了入門難的問題。這裏我們爲大家分享 嚴瀾的博文——如何搭建高效的MongoDB集羣。
此前,我們有分享過該系列博文的第一部分,這裏我們將爲大家分享第二個部分——深入副本集內部機制及分片。
關於博主:嚴瀾現任上海創行科技技術總監。歷任中國平安平臺開發工程師,騰訊拍拍網B2C架構工程師。Web3.0語義搜索引擎探索者,海量數據處理,互聯網高性能低成本平臺架構搭建實踐者,構建讓更多普通開發者快速掌握高性能技術的框架,自由機器人研發愛好者。
以下爲博文:
深入副本集內部機制
該系列文章的第一部分介紹了副本集的配置,這個部分將深入研究一下副本集的內部機制。還是帶着副本集的問題來看吧!
- 副本集故障轉移,主節點是如何選舉的?能否手動干涉下架某一臺主節點。
- 官方說副本集數量最好是奇數,爲什麼?
- MongDB副本集是如何同步的?如果同步不及時會出現什麼情況?會不會出現不一致性?
- MongDB的故障轉移會不會無故自動發生?什麼條件會觸發?頻繁觸發可能會帶來系統負載加重?
Bully算法
MongDB副本集故障轉移功能得益於它的選舉機制。選舉機制採用了Bully算法,可以很方便從分佈式節點中選出主節點。一個分佈式集羣架構中一般都有一個所謂的主節點,可以有很多用途,比如緩存機器節點元數據,作爲集羣的訪問入口等等。主節點有就有吧,我們幹嘛要什麼Bully算法?要明白這個我們先看看這兩種架構:
-
指定主節點的架構,這種架構一般都會申明一個節點爲主節點,其他節點都是從節點,如我們常用的MySQL就是這樣。但是這樣架構我們在第一節說了整個集羣如果主節點掛掉了就得手工操作,上架一個新的主節點或者從從節點恢復數據,不太靈活。
-
不指定主節點,集羣中的任意節點都可以成爲主節點。MongoDB也就是採用這種架構,一但主節點掛了其他從節點自動接替變成主節點。如下圖:
好了,問題就在這個地方,既然所有節點都是一樣,一但主節點掛了,怎麼確定下一個主節點?這就是Bully算法解決的問題。
那什麼是Bully算法,Bully算法是一種協調者(主節點)競選算法,主要思想是集羣的每個成員都可以聲明它是主節點並通知其他節點。別的節點可以選擇接受這個聲稱或是拒絕並進入主節點競爭。被其他所有節點接受的節點才能成爲主節點。節點按照一些屬性來判斷誰應該勝出。這個屬性可以是一個靜態ID,也可以是更新的度量像最近一次事務ID(最新的節點會勝出)。詳情請參考 NoSQL數據庫分佈式算法的協調者競選還有 維基百科的解釋。
選舉
那麼,MongDB是怎進行選舉的呢?官方這麼描述:
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.
大致翻譯過來爲使用一致協議選擇主節點。基本步驟爲:
- 得到每個服務器節點的最後操作時間戳。每個MongDB都有oplog機制記錄本機操作,方便和主服務器進行對比數據是否同步還可以用於錯誤恢復。
- 如果集羣中大部分服務器down機了,保留活着的節點都爲secondary狀態並停止,不選舉了。
- 如果集羣中選舉出來的主節點或者所有從節點最後一次同步時間看起來很舊,停止選舉等待人來操作。
- 如果上面都沒有問題就選擇最後操作時間戳最新(保證數據是最新的)的服務器節點作爲主節點。
這裏提到了一個一致協議(其實就是bully算法),這個和數據庫的一致性協議還是有些區別,一致協議主要強調的是通過一些機制保證大家達成共識;而一致性協議強調的是操作的順序一致性,比如同時讀寫一個數據會不會出現髒數據。一致協議在分佈式裏有一個經典的算法叫“Paxos算法”,後續再介紹。
上面有個問題,就是所有從節點的最後操作時間都是一樣怎麼辦?就是誰先成爲主節點的時間最快就選誰。
選舉觸發條件
選舉不是什麼時刻都會被觸發的,有以下情況可以觸發。
- 初始化一個副本集時。
- 副本集和主節點斷開連接,可能是網絡問題。
- 主節點掛掉。
選舉還有個前提條件,參與選舉的節點數量必須大於副本集總節點數量的一半,如果已經小於一半了所有節點保持只讀狀態。日誌將會出現:
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" }
- ]
- }
如果不想讓一個從節點成爲主節點可以怎麼操作?
- 使用rs.freeze(120)凍結指定的秒數不能選舉成爲主節點。
- 按照上一篇設置節點爲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的設計還真是強大!
後續繼續解決上一節這幾個問題:
- 主節點掛了能否自動切換連接?目前需要手工切換。
- 主節點的讀寫壓力過大如何解決?
還有這兩個問題後續解決(請見下一頁):
- 從節點每個上面的數據都是對數據庫全量拷貝,從節點壓力會不會過大?
- 數據壓力大到機器支撐不了的時候能否做到自動擴展?