SOFAJRaft 選舉機制剖析 | SOFAJRaft 實現原理

SOFAStack
Scalable  Open  Financial  Architecture Stack
是螞蟻金服自主研發的金融級分佈式架構,包含了構建金融級雲原生架構所需的各個組件,是在金融場景裏錘鍊出來的最佳實踐。

默認標題_2019-07-09-0.png

本文爲《剖析 | SOFAJRaft 實現原理》第四篇,本篇做者力鯤,來自螞蟻金服《剖析 | SOFAJRaft 實現原理》系列由 SOFA 團隊和源碼愛好者們出品,項目代號:<SOFA:JRaftLab/>,目前領取已經完成,感謝你們的參與。java

SOFAJRaft 是一個基於 Raft 一致性算法的生產級高性能 Java 實現,支持 MULTI-RAFT-GROUP,適用於高負載低延遲的場景。git

SOFAJRaft :https://github.com/sofastack/sofa-jraftgithub

前言

在 Raft 算法中,選舉是很重要的一部分,所謂選舉也就是在多個節點中選出一個 Leader 節點,由他來對外提供寫服務 (以及默認狀況下的讀服務)。算法

在剖析源碼時,對選舉機制的理解常常會遇到兩極分化的狀況,對於瞭解 Raft 算法基本原理的同窗,閱讀源碼就是品味實現之巧妙的過程,而對初體驗的同窗,卻會陷入丈二和尚的窘境,彷彿墜入雲裏霧裏。架構

爲了提高文章的可讀性,我仍是但願花一部分篇幅講清楚選舉機制的基本原理,以便後面集中注意力於代碼實現。下面是一段圖文比喻,幫助理解的同時也讓整篇文章不至於過早陷入細節的討論。dom

問題1:選舉要解決什麼

一個分佈式集羣能夠當作是由多條戰船組成的一支艦隊,各船之間經過旗語來保持信息交流。這樣的一支艦隊中,各船既不會互相徹底隔離,但也無法像陸地上那樣保持很是密切的聯繫,天氣、海況、船距、船隻戰損狀況致使船艦之間的聯繫存在但不可靠。分佈式

艦隊做爲一個統一的做戰集羣,須要有統一的共識、步調一致的命令,這些都要依賴於旗艦指揮。各艦船要服從於旗艦發出的指令,當旗艦不能繼續工做後,須要有別的戰艦接替旗艦的角色。性能

圖1 - 全部船有責任隨時準備接替旗艦

圖1 - 全部船有責任隨時準備接替旗艦spa

如何在艦隊中,選出一艘獲得你們承認的旗艦,這就是 SOFAJRaft 中選舉要解決的問題。3d

問題2:什麼時候能夠發起選舉

什麼時候能夠發起選舉?換句話說,觸發選舉的標準是什麼?這個標準必須對全部戰艦一致,這樣就可以在標準獲得知足時,全部艦船公平的參與競選。在 SOFAJRaft 中,觸發標準就是通訊超時,當旗艦在規定的一段時間內沒有與 Follower 艦船進行通訊時,Follower 就能夠認爲旗艦已經不能正常擔任旗艦的職責,則 Follower 能夠去嘗試接替旗艦的角色。這段通訊超時被稱爲 Election Timeout (簡稱 ET), Follower 接替旗艦的嘗試也就是發起選舉請求。

圖2 - ET 觸發其餘船競選旗艦

圖2 - ET 觸發其餘船競選旗艦

問題3:什麼時候真正發起選舉

在選舉中,只有當艦隊中超過一半的船都贊成,發起選舉的船纔可以成爲旗艦,不然就只能開始一輪新的選舉。因此若是 Follower 採起儘快發起選舉的策略,試圖儘早爲艦隊選出可用的旗艦,就可能引起一個潛在的風險:可能多艘船幾乎同時發起選舉,結果其中任何一支船都沒能得到超過半數選票,致使這一輪選舉無果,而後失敗的 Follower 們再一次幾乎同時發起選舉,又一次失敗,再選舉 again,再失敗 again ···

圖3 - 同時發起選舉,選票被瓜分

圖3 - 同時發起選舉,選票被瓜分

爲避免這種狀況,咱們採用隨機的選舉觸發時間,當 Follower 發現旗艦失聯以後,會選擇等待一段隨機的時間 Random(0, ET) ,若是等待期間沒有選出旗艦,則 Follower 再發起選舉。

圖4 - 隨機等待時間

圖4 - 隨機等待時間

問題4:哪些候選者值得選票

SOFAJRaft 的選舉中包含了對兩個屬性的判斷:LogIndex 和 Term,這是整個選舉算法的核心部分,也是容易讓人產生困惑的地方,所以咱們作一下解釋:

一、Term

咱們會對艦隊中旗艦的歷史進行編號,好比艦隊的第1任旗艦、第2任旗艦,這個數字咱們就用 Term 來表示。因爲艦隊中同時最多隻能有一艘艦船擔任旗艦,因此每個 Term 只歸屬於一艘艦船,顯然 Term 是單調遞增的。

二、LogIndex

每任旗艦在職期間都會發布一些指令(稱其爲「旗艦令」,類比「總統令」),這些旗艦令固然也是要編號歸檔的,這個編號咱們用 Term 和 LogIndex 兩個維度來標識,表示「第 Term 任旗艦發佈的第 LogIndex 號旗艦令」。不一樣於現實中的總統令,咱們的旗艦令中的 LogIndex 是一直遞增的,不會由於旗艦的更迭而從頭開始計算。

圖5 - 總統令 Vs 旗艦令,LogIndex 稍有區別

圖5 - 總統令 Vs 旗艦令,LogIndex 稍有區別

全部的艦船都儘量保存了過去從旗艦接收到的旗艦令,因此咱們選舉的標準就是哪艘船保存了最完整的旗艦令,那他就最有資格接任旗艦。具體來講,參與投票的船 V 不會對下面兩種候選者 C 投票:一種是 lastTermC < lastTermV;另外一種是 (lastTermV == lastTermC) && (lastLogIndexV > lastLogIndexC)。

稍做解釋,第一種狀況說明候選者 C 最後一次通訊過的旗艦已經不是最新的旗艦了,至少比 V 更滯後,因此它所知道的旗艦令也不可能比 V 更完整。第二種狀況說明,雖然 C 和 V 都與同一個旗艦有過通訊,可是候選者 C 從旗艦處得到的旗艦令不如 V 完整 (lastLogIndexV > lastLogIndexC),因此 V 不會投票給它。

圖6 - Follower 船 b 拒絕了船 c 而投票給船 a,船 a 旗艦令有一個空白框表示「第 Term 任旗艦」沒有發佈過任何旗艦令

圖6 - Follower 船 b 拒絕了船 c 而投票給船 a,船 a 旗艦令有一個空白框表示「第 Term 任旗艦」沒有發佈過任何旗艦令

問題5:如何避免未入流的候選者「搗亂」

如上一小節所說,SOFAJRaft 將 LogIndex 和 Term 做爲選舉的評選標準,因此當一艘船發起選舉以前,會自增 Term 而後填到選舉請求裏發給其餘船隻 (多是一段很複雜的旗語),表示本身競選「第 Term + 1 任」旗艦。

這裏要先說明一個機制,它被用來保證各船隻的 Term 同步遞增:當參與投票的 Follower 船收到這個投票請求後,若是發現本身的 Term 比投票請求裏的小,就會自覺更新本身的 Term 向候選者看齊,這樣可以很方便的將 Term 遞增的信息同步到整個艦隊中。

圖7 - Follower 船根據投票請求更新本身的 Term

圖7 - Follower 船根據投票請求更新本身的 Term

可是這種機制也帶來一個麻煩,若是一艘船由於本身的緣由沒有看到旗艦發出的旗語,他就會自覺得是的試圖競選成爲新的旗艦,雖然不斷髮起選舉且一直未能當選(由於旗艦和其餘船都正常通訊),可是它卻經過本身的投票請求實際擡升了全局的 Term,這在 SOFAJRaft 算法中會迫使旗艦 stepdown (從旗艦的位置上退下來)。

圖8 - 自覺得是的搗亂者,迫使旗艦 stepdown

圖8 - 自覺得是的搗亂者,迫使旗艦 stepdown

因此咱們須要一種機制阻止這種「搗亂」,這就是預投票 (pre-vote) 環節。候選者在發起投票以前,先發起預投票,若是沒有獲得半數以上節點的反饋,則候選者就會識趣的放棄參選,也就不會擡升全局的 Term。

圖9 - Pre-vote 預投票

圖9 - Pre-vote 預投票

選舉剖析

在上面的比喻中,咱們能夠看到整個選舉操做的主線任務就是:

  1. Candidate 被 ET 觸發
  2. Candidate 開始嘗試發起 pre-vote 預投票
  3. Follower 判斷是否定可該 pre-vote request
  4. Candidate 根據 pre-vote response 來決定是否發起 RequestVoteRequest
  5. Follower 判斷是否定可該 RequestVoteRequest
  6. Candidate 根據 response 來判斷本身是否當選

這個過程可用下圖表示:

圖10 - 一次成功的選舉

圖10 - 一次成功的選舉

在代碼層面,主要是由四個方法來處理這個流程:

com.alipay.sofa.jraft.core.NodeImpl#preVote //預投票
com.alipay.sofa.jraft.core.NodeImpl#electSelf //投票
com.alipay.sofa.jraft.core.NodeImpl#handlePreVoteRequest //處理預投票請求
com.alipay.sofa.jraft.core.NodeImpl#handleRequestVoteRequest //處理投票請求

代碼邏輯比較直觀,因此咱們用流程圖來簡述各個方法中的處理。

預投票和投票

圖11 - 預投票 Vs 投票

圖11 - 預投票 Vs 投票

圖中可見,預投票請求 preVote 和投票請求 electSelf 的流程基本相似,只是有幾個細節不太同樣:

  1. preVote 是由超時觸發;
  2. preVote 在組裝 Request 的時候將 term 賦值爲 currTerm + 1,而 electSelf 是先將 term ++;
  3. preVote 成功後,進入 electSelf,electSelf 成功後 become Leader。

處理請求

處理預投票和投票請求的邏輯也比較相似,一樣用圖來表示。

圖12 - 處理預投票請求

圖12 - 處理預投票請求

圖13 - 處理投票請求

圖13 - 處理投票請求

圖中可見,處理兩種請求的流程也基本相似,只是處理投票請求的時候,會有 stepdown 機制,強制使 Leader 從其 Leader 的身份退到 Follower。在具體的實現中,Leader 會經過租約的機制來避免一些沒有必要的 stepdown,關於租約機制,能夠參見以前的系列文章《SOFAJRaft 線性一致讀實現剖析 | SOFAJRaft 實現原理》。

總結

咱們在本文中採用了類比的方式來剖析源碼,主要是爲了讓讀者能更容易的理解如何在分佈式環境中達成共識,其實這也是整個 SOFAJRaft 要實現的目標。

行文至此,做者感受已經把選舉說清楚了,若是還有沒提到的地方,或者一些流程中的分支任務,歡迎從源碼中進一步尋找答案。最後貼出上面提到的四個方法的源碼。

圖14 - preVote 預投票

圖14 - preVote 預投票

圖15 - electSelf 投票

圖15 - electSelf 投票

圖16 - handlePreVoteRequest 處理預投票

圖16 - handlePreVoteRequest 處理預投票

圖17 - handleRequestVoteRequest 處理投票

圖17 - handleRequestVoteRequest 處理投票

《剖析 | SOFAJRaft 實現原理》系列文章回顧

公衆號:金融級分佈式架構(Antfin_SOFA)

相關文章
相關標籤/搜索