1. 程式人生 > >Raft與MongoDB複製集協議比較

Raft與MongoDB複製集協議比較

一文搞懂raft演算法一文中,從raft論文出發,詳細介紹了raft的工作流程以及對特殊情況的處理。但演算法、協議這種偏抽象的東西,僅僅看論文還是比較難以掌握的,需要看看在工業界的具體實現。本文關注MongoDB是如何在複製集中使用raft協議的,對raft協議做了哪些擴充套件。

閱讀本文,需要對MongoDB複製集replication有一定認識,特別是replicat set protocol version

帶著問題學習分散式系統之中心化複製集一文中,介紹了中心化副本控制協議。在raft(mongodb pv1)中,也是通過先選舉出leader(primary),然後通過leader(primary)管理整個複製集。

在3.2以及之後的版本中,mongodb預設使用protocol version 1。從官方的一些資料、視訊可以看到,這個是一個raft-like的協議。本文主要從leader-election和log replication這兩個角度來對比mongodb rs pv1與raft,並試圖分析差異的原因。

需要注意的是,本文所有對MongoDB複製集的分析都是基於MongoDb3.4

本文地址:https://www.cnblogs.com/xybaby/p/10165564.html

leader election

首先對raft協議中leader election做幾點總結:

  1. 同一任期內最多隻能投一票,先來先得
  2. 選舉人必須比自己知道的更多(比較term,log index)
  3. 為了understandability,raft中節點之間沒有ranking,公平參與投票

選舉、投票資格

為了簡化協議,使得raft更容易理解,raft中所有節點都能發起選舉、參與投票。但在MongoDB中,有更為豐富的選舉控制策略,我們從Replica Set Configuration就能看出來,replica set中的節點可以配置以下屬性

  members: [
    {
      _id: <int>,
      host: <string>,
      arbiterOnly: <boolean>,
      buildIndexes: <boolean>,
      hidden: <boolean>,
      priority: <number>,
      tags: <document>,
      slaveDelay: <int>,
      votes: <number>
    },
    ...
  ],
  • arbiterOnly: Arbiter上沒有使用者資料,只能投票,不能發起選舉,其作用在於用盡量少的資源使得複製集中節點數目為奇數。
  • hidden:雖然有資料,但對客戶端不可見,可以用來做備份等其他用途。hidden的priority一定是0,因此不可以發起選舉,但是可以投票
  • priority:A number that indicates the relative eligibility of a member to become a primary. priority為0時是不能發起選舉的。
  • votes:是否可以參與投票,mongodb複製集中最多可以有50個節點,但最多隻有7個可以投票,其作用在於降低複雜度。

priority

這裡再單獨強調一下priority,mongodb中

Changing the balance of priority in a replica set will trigger one or more elections. If a lower priority secondary is elected over a higher priority secondary, replica set members will continue to call elections until the highest priority available member becomes primary.

通過rs.reconfig()修改節點的優先順序的時候,會觸發重新選舉。整個複製集會不斷髮起選舉,直到最高優先順序的節點成為primary。當然,在選舉-投票的過程中,還是必須滿足候選者資料足夠新的約束。

priority很有用,比如在multi datacenter deploy的情況下,我們可能根據使用者的分佈情況來確定primary在哪個datacenter。

heartbeat

raft中,只有leader給follower發心跳資訊(心跳是沒有log-entry的Append Entries rpc),然後follower回覆心跳訊息。

Followers are passive: they issue no requests on their own but simply respond to requests from leaders and candidates.

在mongodb中,節點兩兩之間有心跳

Replica set members send heartbeats (pings) to each other every two seconds. If a heartbeat does not return within 10 seconds, the other members mark the delinquent member as inaccessible.

heartbeat

primary handover

在raft中,只有當leader收到來自term更高的節點的訊息時,才會切換到follower狀態。如果出現網路分割(network partition),那麼這個過期的leader還會一直認為自己是leader

If a candidate or leader discovers
that its term is out of date, it immediately reverts to follower
state.

在mongodb中,primary在election timeout時間還沒有收到來自majority 節點的訊息時,會主動切換成secondary。這樣可以避免過期的Primary(stale primary)繼續對外提供服務,尤其是MongoDB允許writeConcern:1.

選舉過程 - 預投票

raft中,在election timeout超時後,立即會發起選舉,執行以下操作

  • 增加節點本地的 current term ,切換到candidate狀態
  • 投自己一票
  • 並行給其他節點發送 RequestVote RPCs
  • 等待其他節點的回覆
  • 如果得到majority投票,成為leader

mongodb增加了一個預投票的過程(dry-run),即在不增加新的term的情況下先問問其他節點,是否可能給自己投票,得到大多數節點的肯定回覆之後才會發起真正的選舉過程。其作用在於儘可能減少不必要的主從切換,這部分後面還會提到。

log replication

複製集中,各個節點資料的一致性是必須要解決的問題。而對於客戶端(應用)而言,複製集則需要承諾已提交的資料不能回滾。

同步or非同步

帶著問題學習分散式系統之中心化複製集一文中,介紹了複製集中資料的兩種複製方式,並分析了各自的優缺點。簡而言之,同步方式可靠性更高,但可用性更差,網路延時更大;非同步模式則恰好相反。

raft協議則是這兩種方式的折中,當log複製到了大多數的節點就可以向客戶端返回了。大多數節點既保證了資料的可靠性:資料不會被回滾;又保證了有較高的可用性:只有有超過一半節點存活整個系統就能正常工作。

MongoDB通過Write Concern選項將選擇權交給了使用者,使用者可以根據實際情況來選擇將資料複製到了多少節點再向客戶端返回。writeconcern有三個引數

  • w:寫到多少節點即可向客戶端返回
    • 1,預設值,即寫primary即可返回,效能最高,延遲最低
    • majority,同raft,寫到大多數節點才返回
    • tag set,寫到指定的節點才返回,用於特殊場景
  • j:是否寫到journal(保證持久化)
  • wtimeout:多長時間如果沒有寫到w個節點就向客戶端返回錯誤

由於預設寫到primary即可向客戶端返回,那麼不難想到,如果oplog尚未同步到secondary,primary掛掉,那麼新選舉出來的Primary可能沒有最新的已經向客戶端確認的資料,導致資料的回滾,後面會提到mongodb通過catchup來儘量避免回滾。

data flow

帶著問題學習分散式系統之中心化複製集中也給出了兩種資料從primary到secondary的方式:主從模式,鏈式模式。其中,主從模式是priamry推送給所有的secondary,顯然raft就是這種模式。

而在MongoDB中,可以通過引數settings.chainingAllowed控制使用主從模式,還是鏈式模式。預設值為True,即預設情況下,mongodb中secondary可以從其他secondary同步資料,這樣secondary可以選擇一個離自己最近(心跳延時最小的)節點來複制oplog,在MongoDB中,稱oplog的同步源為SyncSource。

push or pull

raft中,leader並行將資料push到follower。而在MongoDB中,primary將資料寫到local.oplog.rs,secondary定期從其SyncSource(參考上一節,不一定是從priamry拉資料,也可能是從其他secondary)讀取oplog,並應用到本地。

深入淺出MongoDB複製一文中給出了一個oplog拉取的流程

MongoDB選擇了pull的策略,顯然會加大在writeConcern: majority時的延遲,但對於預設的鏈式複製,pull是更合適的,因為secondary更清楚自己的SyncSource。

append vs apply

在諸多共識演算法中,都是將command封裝到有序、持久化的log當中,raft和MongoD也是如此。

對於raft,leader先將log先append到本地的log entries,然後等到收到majority節點的回覆後再apply log到狀態機,如下入所示:

但是在mongodb中,即使客戶端要求writeconcern:majority,primary也是先apply,將變更作用到狀態機,再寫oplog。之後,secondary再從其SyncSource的local.oplog.rs collection 拉取oplog,本地apply,然後寫oplog。

MongoDB先Apply再寫oplog,以及非同步複製的機制,會導致即使資料無法寫到大多數節點(可能primary與其他節點間網路故障),即使向客戶端返回寫入失敗,寫到primary的資料也不會回滾。

catchup

catchup既與write concern有關,也跟leader election有關。

mongodb中,有這麼一個引數settings.catchUpTimeoutMillis, 其作用是

Time limit in milliseconds for a newly elected primary to sync (catch up) with the other replica set members that may have more recent writes.
The newly elected primary ends the catchup period early once it is fully caught up with other members of the set. During the catchup period, the newly elected primary is unavailable for writes from clients.

也就是說,在primary選舉出來之後,會有一段時間,讓primary嘗試去其他節點讀取到更新的寫操作(more recent)。直到追加到最新的oplog,或者超時,primary才進入工作狀態(接收客戶端寫請求)

究其原因,MongoDB允許使用者自定義writeconcern,且預設只要求寫到primary。因此選舉的時候即使得到了大多數節點的投票,且primary的資料在這些大多數節點中是最新的,但原來的primary可能沒有參與投票,那麼就可能導致資料的回滾。catchup能夠儘量避免回滾的出現,如果無法在settings.catchUpTimeoutMillis時間內完成catchup,也會將回滾的內容寫入一個rollback檔案。

差異的思考

MongoDB作為一個分散式資料庫系統,既要支援OLTP,又要支援OLAP,既要滿足水平伸縮,又要保證高可用、高可靠,還要支援分散式事務(Mongodb 4.x)。因此為了儘量滿足不同場景下的業務需求,MongoDB提供了大量的選項,供使用者選擇,更加靈活。對於複製集這一塊而言,選項包括但不限於:

  • WriteConcern
  • ReadConcern
  • ReadPreference
  • settings.chainingAllowed
  • settings.catchUpTimeoutMillis

所以,作為MongoDB的使用者,首先得清楚這些可選項的意義,然後根據自己的業務需求,合理配置。

從這些選項的預設值以及MongoDB的實現,個人覺得,在CAP這個問題上,MongoDB應該是更傾向於A(availability,可用性)的。

而諸如鏈式複製,Leader Priority這些特性,在分散式系統的部署層面來說都是很有用的,比如multi datacenter,很多分散式儲存系統也支援同樣的特性。Raft協議雖然說是為工業實現提供了很好的指導,但到具體的應用,還是得有諸多的調整和完善。

dry-run or pre-vote

MongoDB中的預投票是對raft協議很好的改進,之前,我在看Raft論文的時候也想到了一些corner case,在論文中並沒有很清楚的闡述,但預投票能很好解決這些問題。

事實上,MongoDB中的預投票(dry-run)並不是獨創的,在raft協議的超長版解釋Consensus: Bridging Theory and Practice中,raft協議的作者就建議實現pre-vote來增加系統的魯棒性。而在Four modifications for the Raft consensus algorithm(PS,該文的作者就是MongoDB的開發者)詳細闡述了Pre-vote的原因以及實現方法。Pre-vote是為了防止一個隔離的follower不斷髮起選舉 導致term值的激增,以及不必要的主從切換。

如上圖所以,系統由s1 s2 s3三個節點組成,其中s1是leader,另外兩個節點是follower。

pre-vote考慮的是這樣一種情況,(s2)與(s1 s3)之間出現了網路分割(network partition),那麼按照raft演算法,s2會不斷的嘗試發起選舉,意味著不斷的增加term。那麼當網路自愈之後,s2將訊息傳送到s1 s3. 按照raft論文figure2 Rules for servers

If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower

因此s1 會切換到follower, s1 s3 term修改為57,但s2的log 大概率是過舊的(out of date),因此s2無法獲得選舉,s1 s3會在election timeout後發起選舉,其中一個成為term 58的leader。

pre-vote 避免了term inflation,但更重要的是,避免了一次沒有必要的重新選舉: s1一定會切換到follower,然後s1或者s3再次發起選舉,在這個過程中,由於沒有leader,整個系統其實是不可用的(至少不可寫)。

references

一文搞懂raft演算法
replication
MongoDB and Raft
MongoDB複製集技術內幕:工作原理及新版本改進方向
MongoDB 高可用複製集內部機制:Raft 協議
Consensus: Bridging Theory and Practice
Four modifications for the Raft consensus algorithm