分佈式一致性協議 Raft論文筆記

Raft論文筆記


Raft原版英文論文https://raft.github.io/raft.pdf

論文名稱是尋找一種易於理解的一致性算法(擴展版)。Paxos一致性算法在解決分佈式系統一致性方面一直是業界的標杆,但是由於其複雜性,導致Paxos難以理解,並且Paxos自身的算法結構需要進行大量的修改才能應用到實際的系統中。因此Raft一致性算法就應運而生。Raft算法通過將一致性問題分解爲多個子問題(Leader election、Log Replication、Safety、Log Compaction、Membership change)來提升算法的可理解性。因此我先會對Raft算法整體運行機制進行簡單綜述,然後針對性的對每個子問題寫一下我自己的理解,如果有不對的地方希望各位看官及時指出。

一、Raft算法綜述

一致性問題

一致性問題在分佈式存儲系統中一直是個難點,也是重點。分佈式存儲系統爲了滿足可用性(Availability),必須通過維護多個副本進行容錯。當分佈式系統中存在多個副本時,這些副本的一致性問題就又成了一個焦點問題。

一致性是分佈式領域最爲基礎也是最重要的問題。如果分佈式系統能實現一致,對外就可以呈現出一個完美的、可擴展的”虛擬節點“,相對物理節點具備更優越的性能和穩定性。在分佈式系統中,運行着多個相互關聯的服務節點。一致性是指分佈式系統中的多個服務節點,給定一系列的操作,在約定協議的保障下,使它們對外界呈現的狀態是一致的。在一個具有一致性的性質的集羣裏面,同一時刻所有的結點對存儲在其中的某個值都有相同的結果,即對其共享的存儲保持一致。

架構

在這裏插入圖片描述

Raft算法從多副本狀態機角度出發,用於管理多副本狀態機的日誌複製。圖中可以看出replicated log會最終應用在replicated status machine中。

複製狀態機通常都是基於複製日誌實現的,如上圖所示。每一個服務器存儲一個包含一系列指令的日誌,並且按照日誌的順序執行。每一個日誌都按照相同的順序包含相同的指令,所以每一個服務器都執行相同的指令序列。因爲每個狀態機都是確定的,每一次執行操作都產生相同的狀態和同樣的序列。因此只要保證所有服務器上的日誌都完全一致,就可以保證state machine的一致性。日誌的一致性是由一致性模塊來保證的。

在這裏插入圖片描述

Figure1圖中從client向server發出請求的交互過程說明一致性模塊在replicated state machine中的作用。

  1. 客戶端向服務端發起請求,執行指定操作
  2. Consensus Module將操作指令以日誌的形式備份到其他備份實例上
  3. 當日志中的log entry按照統一的順序成功備份到各個實例上,日誌中的log entry將會被應用到上層狀態機
  4. 服務端返回操作結果至客戶端

整個複製狀態機架構中最重要的就是一致性模塊,保證複製日誌相同就是一致性算法的工作。在一臺服務器上,一致性模塊接收客戶端發送來的指令然後增加到自己的日誌中去。它和其他服務器上的一致性進行通信來保證每一個服務器上的日誌最終都以相同的順序包含相同的請求,儘管有些服務器會宕機,但是要保證服務可用的話,宕機的服務器必須少於整體集羣主機數的一半。一旦指令被正確的複製,每一個服務器的狀態機按照日誌順序處理log entry,然後輸出結果被返回給客戶端。因此,服務器集羣對外界呈現的是一個高可靠的狀態機。

二、Raft基礎

一個Raft集羣包含若干個服務器節點;如果節點數是5個,這允許整個系統容忍2個節點的失效。在任何時刻,每一個服務器節點都處於這三個狀態之一:領導人(Leader)、跟隨者(Follower)、候選人(Candidate).

  • Leader:所有請求的處理者,其實就是整個Raft集羣和外部進行溝通的接口人。Leader接收client的更新請求,本地處理後再同步至多個其他副本;
  • Follower:請求的被動更新者,從Leader接收更新請求,然後寫入本地日誌文件;
  • Candidate:如果Follower副本在一段時間內沒有收到Leader的心跳,則判斷Leader可能已經發生故障,此時啓動Leader election,Follower副本會變成Candidate狀態,直至選主結束。

在這裏插入圖片描述

Figure4圖中表示服務器在Follower、Candidate、Leader三個狀態之間的轉換。如果Follower在一定時間內沒有收到來自Leader的消息,會轉換爲Candidate狀態並觸發election。獲得集羣中大多數節點選票的Candidate將轉換爲Leader。在一個任期內,Leader一直都會保持Leader狀態除非自己宕機了。
在這裏插入圖片描述

Figure5:時間被劃分成一個個的任期,每個任期開始都是一次選舉。在選舉成功後, 領導人會管理整個集羣直到任期結束。有時候選舉會失敗,那麼這個任期就會以沒有領導人而結束。任期之間的切換可以在不同的服務器上觀察到。

Raft把時間分割成任意長度的任期(term),任期的概念我們可以理解爲“皇帝的年號”。每個當選的領導人都有自己的任期。term有唯一的id。每一段任期從一次選舉開始,一個或者多個Candidate嘗試成爲領導者。如果一個Candidate最終贏得選舉,然後他就在接下來的term內充當領導人的職責。在某些情況下,一次選舉過程會造成選票的瓜分。在這種情況下,這一term會以沒有領導人結束;一個新的term會以一次新的選舉而重新開始。Raft保證了在一個給定的任期內,最多隻有一個領導者。

不同的server可能多次觀察到任期之間的轉換,但在某些情況下,一個節點也可能觀察不到任何一次選舉或者整個任期全程(比如節點宕機了)。任期在Raft算法中充當logic clock,這允許server可以查明一些過期的信息比如陳舊的領導者。每一個節點存儲一個當前的任期號(current_term_id),term_id在整個時期內是單調增長的。當服務器之間通信的時候會交換current_term_id;如果一個server的current_term_id比其他server小,那麼它會更新自己的current_term_id到較大的編號值。如果一個Candidate或者Leader發現自己的current_term_id過期了,那麼他會立即恢復成Follower狀態。如果一個節點接收到一個包含過期的current_term_id的請求,那麼他會直接拒絕這個請求。

Raft算法中server節點之間通信使用遠程過程調用(RPCs),並且基本的一致性算法只需要兩種類型的RPCs。請求投票(RequestVote) RPCs由候選人在選舉期間發起,然後附件條目(AppendEntries) RPCs由領導人發起,用來複制日誌和提供一種心跳機制。後面爲了在server之間傳輸snapshot增加了第三種RPC。當服務器沒有及時的收到RPC響應,會進行重試,並且他們能夠並行的發起RPCs來獲得更高的性能。

三、領導人選舉

Raft使用一種心跳機制來觸發領導人選舉。當服務器程序啓動時,他們都是Follower角色。一個server節點繼續保持着Follower狀態只要它從Leader或者Candidate處接收到有效的RPCs。Leader週期性的向所有Follower發送心跳包(其實就是不包含日誌項內容的AppendEntries RPCs)來維持自己的領導權威。如果一個Follower在一段時間內沒有接收到任何消息,也就是發生了timeout,那麼他就會認爲系統中沒有Leader,因此自己就會發起選舉以選出新的Leader。

每一段term一開始的選舉過程:

  1. Follower將自己維護的current_term_id+1;
  2. 然後轉換狀態爲Candidate;
  3. 發送RequestVoteRPC消息(會帶上自己的current_term_id)給其他所有server;

要開始一次選舉過程,Follower先要增加自己的current_term_id並轉換到Candidate狀態。然後它會並行的向集羣中的其他服務器節點發送RequestVoteRPC來給自己投票。Candidate會繼續保持當前狀態直到以下三種情況出現:

  1. 自己已經贏得了選舉,成功被選舉爲Leader。當收到了大多數節點(majority)的選票後,角色狀態會轉換爲Leader,之後會定期給其它所有server發心跳信息(不帶log entry的AppendEntries RPC),用來告訴對方自己是當前term(也就是發送RequestVoteRPC時附帶的current_term_id)的Leader。每個term最多隻有一個leader,term id作爲logical clock,在每個RPC消息中都會帶上,用於檢測過期的消息。當一個server收到的RPC消息中的rpc_term_id比本地的current_term_id更大時,就更新current_term_id爲rpc_term_id,並且如果當前節點的角色狀態爲leader或者candidate時,也會將自己的狀態切換爲follower。如果rpc_term_id比接收節點本地的current_term_id更小,那麼RPC消息就被會拒絕。
  2. 其他節點最終成功被選舉爲Leader。當Candidate在等待投票的過程中,收到了rpc_term_id大於或者等於本地的current_term_id的AppendEntriesRPC消息時,並且這個RPC消息聲明自己是這個任期內的leader。那麼收到消息的節點將自己的角色狀態轉換爲follower,並且更新本地的current_term_id。
  3. 第三種可能的結果是Candidate既沒有贏得選舉也沒有輸,本輪選舉沒有選出leader,這說明投票被瓜分了。沒有任何一個Candidate收到了majority的投票時,leader就無法被選出。這種情況下,每個Candidate等待的投票的過程就出現timeout,隨後candidates都會將本地的current_term_id+1,再次發起RequestVoteRPC進行新一輪的leader election。
  • 每個節點只會給每個term投一票
  • Raft算法使用隨機選舉超時時間的方法來確保很少會發生選票被瓜分的情況,就算髮生也能很快的解決。爲了阻止選票起初就被瓜分,選舉超時時間是從一個固定的區間(150-300ms)隨機選擇。這樣可以把服務器都分散開以至於在大多數情況下只有一個服務器會選舉超時;然後他贏得選舉並在其他服務器超時之前發送心跳包。同樣的機制也用在了選票被瓜分的情況下。當選票被瓜分,所有candidate同時超時,有很大可能又進入新一輪的選票被瓜分循環中。爲了避免這個問題,每個candidate的選舉超時時間從150-300ms中隨機選取,那麼第一個超時的candidate就可以率先發起新一輪的leader election,帶着最大的current_term_id給其他所有server發送RequestVoteRPC消息,從而自己成爲leader,然後給他們發送AppendEntriesRPC以告訴他們自己是leader。

四、日誌複製

一旦一個leader被選舉出來,他就開始爲客戶端提供服務,接受客戶端發來的請求。每個請求包含一條需要被replicated state machine執行的指令。leader會把每條指令作爲一個最新的log entry添加到日誌中,然後併發的向其他服務器發起AppendEntriesRPC請求,讓他們也複製這條指令。當leader確認這條log entry被安全地複製(大多數副本已經改日誌指令寫入本地日誌中),leader就會將這條log entry應用到狀態機中然後返回結果給客戶端。如果follower崩潰或者運行緩慢,沒有成功的複製日誌,leader會不斷的重複嘗試AppendEntriesRPCs(儘管已經將執行結果返回給客戶端)直到所有的follower都最終存儲了所有的log entry。
在這裏插入圖片描述

Figure6:日誌由有序序號標記的條目組成。每個條目都包含創建時的任期號和一個狀態機需要執行的指令。一個條目當可以安全的被應用到狀態機中去的時候,就認爲是可以提交了。

日誌的基本組織結構:

  • 每一條日誌都有日誌序號log index
  • 每一條日誌條目包含狀態機要執行的日誌指令(x←3)、該日誌指令對應的term

leader來決定什麼時候把log entry應用到狀態機中是安全的;這種log entry被稱爲已提交。Raft算法保證所有commited的log entry都是持久化的並且最終會被所有可用的狀態機執行。在leader創建的log entry複製到大多數的服務器節點的時候,log entry就會被提交。同時,leader的日誌中之前的所有log entry也都會被提交,包括由其他leader創建的條目。一旦follower知道一條log entry已被提交,那麼這個節點也會將這個 log entry按照日誌的順序應用到本地的狀態機中。

Raft算法日誌機制有以下2個特性:

  • 如果在不同的日誌中的兩個log entry擁有相同的index和term_id,那麼他們存儲了相同的指令
  • 如果在不同的日誌中的兩個log entry擁有相同的index和term_id,那麼他們之前的所有log entry也全部相同

第一個特性是因爲leader最多在一個任期裏在指定的一個日誌索引位置創建一個log entry,同時log entry在日誌中的位置也從來不會改變。

第二個特性是通過AppendEntriesRPC的一個簡單的一致性檢查保證。在發送AppendEntriesRPC時,leader會把新的log entry緊接着之前的log entry的index和term_id一起包含在內。如果follower在它的日誌中找不到包含相同index和term_id的log entry,那麼他就會拒絕接收新的log entry。這種一致性檢查會保證每一次新追加的log entry的一致性。一開始空的日誌狀態肯定滿足日誌匹配特性,然後在日誌擴展時AppendEntriesRPC的一致性檢查保護了這種特性。

在正常情況下,leader和follower的日誌保持一致性。但是leader崩潰會使得日誌處於不一致的狀態。當一個新leader被選出來時,它的日誌和其他的follower的日誌可能不一樣,這時就需要一個機制來保證日誌的一致性。一個新leader產生時,集羣狀態可能會像下圖一樣:
在這裏插入圖片描述

當新leader成功當選時,follower可能是任何情況(a-f)。每個格子表示是一個log entry;裏面的數字表示term_id。follower可能會缺少一些log entry(a-b),可能會有一些未被提交的log entry(c-d),或者兩種情況都存在(e-f)。

簡單解釋一下場景f出現的情況:某個服務器節點在任期2的時候是leader,

因此需要一種機制來讓leader和follower對log達成一致。leader會爲每個follower維護一個nextIndex,表示leader給各個follower發送的下一條log entry在log中的index,初始化爲leader的最後一條log entry的下一個位置。leader給follower發送AppendEntriesRPC消息,帶着(term_id,(nextIndex-1)),term_id是索引位置爲(nextIndex-1)的log entry的term_id,follower接收到AppendEntriesRPC後,會從自己的log的對應位置找是否有log entry能夠完全匹配上。如果不存在,就給leader回覆拒絕消息,然後leader再將nextIndex-1,再重複,直至AppendEntriesRPC消息被follower接收,也就是leader和follower的log entry能夠匹配。

以leader和f爲例:

leader的最後一條log entry的index是10,因此初始化時,nextIndex=11,leader發f發送AppendEntriesRPC(6,10),f在節點本地日誌的index=10的位置上沒有找到term_id=6的log entry。則給leader迴應一個拒絕消息。隨後leader將nextIndex-1,變爲10,然後給f發送AppendEntriesRPC(6,9),f在自己的log的index=9的位置沒有找到term_id=6的log entry。匹配過程會一直循環下去直到leader和follower的日誌能夠匹配。當leader發送了AppendEntriesRPC(1,3),f在自己log的index=3的位置找到了term_id爲1的log entry。成功接收leader的消息。隨後,leader就開始從index=4的位置開始給f推送日誌。

五、安全性

前面寫的內容描述了Raft算法是如何選舉和複製日誌的。然而,到目前爲止描述的機制並不能充分的保證每一個狀態機會按照相同的順序執行相同的指令。比如一個follower可能會進入不可用狀態時領導人已經提交了多條log entry,隨後這個follower恢復後可能會被選舉爲leader並覆蓋這些log entry。因此導致了不同的狀態機可能會執行不同的日誌指令。

因此這節要討論的就是哪些follower有資格成爲leader

Raft保證被選爲新leader的節點擁有所有committed的log entry,這與ViewStamped Replication不同,後者不需要這個保證,而是通過其他機制從follower拉取自己沒有提交的日誌記錄。

這個保證是在RequestVoteRPC階段實現的,candidate在發送RequestVoteRPC時,會帶上自己的最後一條log entry的term_id和index,其他節點收到消息時,如果發現自己的本地日誌比RPC請求中攜帶的更新,則拒絕投票。日誌比較的原則是,如果本地的最後一條log entry的term_id更大,則更新。如果term_id相同,則日誌條目更多的一方更新(index大的一方日誌條目最多)。
在這裏插入圖片描述

  1. 在階段a,term=2,S1是leader,且S1寫入日誌(term,index)爲(2,2),日誌也被同步寫入了S2;
  2. 在階段b,S1離線,觸發一次新的選主,此時S5被選爲新的Leader,此時term=3,且寫入了日誌(term,index)爲(3,2);
  3. S5尚未將日誌推送到Followers變離線了,從而又觸發了一次新的選主,而之前離線的S1經過重新上線後被選中爲leader,此時系統term=4。隨後S1會將自己的日誌同步到Followers,圖c就是將日誌(2,2)同步到S3,而此時由於該log entry已經被同步到了多數節點(S1,S2,S3),因此log entry(2,2)可以被commit;
  4. 在階段d,S1又變爲離線,系統觸發一次選主,而S5有可能被選爲新的leader。S5滿足競選成爲leader的條件:1.S5的最後一個log entry的term=3,多數節點的最後一條log entry的term=2;2.最新的日誌index=2,比大多數節點的日誌都新。因此當S5成功被選爲新的leader後,會將自己的日誌更新到Followers,於是S2、S3中已經被提交的日誌(2,2)被覆蓋了。然而一致性協議中不允許出現已經apply到state machine中的日誌被覆蓋。

因此爲了避免發生這種錯誤,需要對協議進行微調:

只允許Leader提交當前term的日誌

經過微調後,即使日誌(2,2)已經被多數節點(S1、S2、S3)確認了,但是不能被commit,因爲當前term=4,而(2,2)是來自之前term=2的日誌,直到S1在當前term=4產生的日誌(4,3)被大多數Followers確認,S1才能夠commit(4,3)這條日誌。而根據Raft機制,leader複製本地日誌到各個Followers時,會通過AppendEntriesRPC進行一致性檢查。(4,3)之前的所有日誌也會被commit。此時即使S1再下線,重新選主時S5也不可能選主成爲leader,因爲它沒有包含大多數節點已經擁有的日誌(4,3).

  • 什麼時候一條log entry可以被認爲是commited?

leader正在replicate當前term的日誌記錄給follower,一旦leader確認了這條log entry被majority寫盤了,這條log entry就被認爲commited。如Figure8中的圖a,S1作爲term2階段的leader,如果index=2的log entry被majority寫盤了,這條log entry就被認爲是commited

leader正在replicate更早的term的log entry給其它follower,如圖c。S1是term4階段的leader,正在將term=2,index=2的log entry複製給其它follower。

Follower和Candidate崩潰

前面我們講到了Leader崩潰的情況。Follower和Candidate崩潰後的處理方式比Leader崩潰的情況要簡單的多,並且他們的處理方式是相同的。如果Follower或Candidate崩潰,那麼後續發送給他們的RPCs都會失敗。Raft中處理這種失敗就是簡單的通過無限重試;如果崩潰的服務器節點重啓了,那麼這些RPC就會成功發送到這些服務器節點上。如果一個服務器在完成了一個RPC後,但是還沒有來得及響應的時候崩潰了,那麼在節點重新啓動之後就會再次收到同樣的RPC請求。Raft的RPCs都是冪等的,所以無線重試不會造成任何問題。例如一個Follower如果收到AppendEntriesRPC但是他已經包含了這一條log entry,那麼Follower就會直接忽略這個新的請求。

六、日誌壓縮

Raft的日誌在正常操作中會不斷的增長,但是在實際的系統中,日誌不能無限制的增長。隨着日誌不斷增長,會佔用越來越多的空間,花費越來越多的時間來重置。如果沒有一定的機制去清除日誌裏積累的陳舊的信息,日誌會無限增長,否則系統重啓時需要花很長的時間進行回放,從而影響系統可用性。

snapshot是最簡單的壓縮方法。在snapshot中,整個系統的狀態都以snapshot的形式寫入到穩定的持久化存儲中,然後snapshot之前的日誌都可以丟棄。Snapshot技術在Chubby和Zookeeper系統中都有采用。

Raft的日誌壓縮方案是每個副本獨立的對自己的系統狀態進行snapshot,並且只能對commited的日誌記錄(已經apply到state machine)進行snapshot。

在這裏插入圖片描述

圖12展示了一個服務器節點用新的快照替換了從index1到index5的log entry,snapshot存儲了當前的狀態。snapshot包含了最後的索引位置和term_id。每個服務器獨立的創建snapshot,只包括已經提交的日誌。主要的工作包括將狀態機的狀態寫入到snapshot中。Snapshot中包含以下內容:

  • 日誌元數據,最後一條commited log entry的(log index,last_included_term)。這兩個值在snapshot之後的第一條log entry的AppendEntriesRPC的一致性檢查的時候會被用上。log index(最後被包含索引)指的是被快照取代的最後的條目在日誌中的索引值(state machine最後應用的日誌),last_included_term(最後被包含的任期)指的是該條目的任期號。保留這些數據是爲了支持快照後緊接着的第一個條目的AppendEntriesRPC的一致性檢查,因爲這個條目需要前一日誌條目的索引值和任期號。

snapshot的缺點是不是增量的,即使內存中某個值沒有變化,下次做snapshot的時候仍然會被備份到磁盤。當leader需要發給某個Follower的log entry被丟棄了(因爲leader做了snapshot),leader會將snapshot發給落後太多的Follower。或者當新加一臺節點時,會發送snapshot給新節點。發送snapshot使用新的RPC:InstalledSnapshot

做snapshot有一些需要注意的點:

  1. snapshot操作不要太頻繁,否則磁盤帶寬消耗很嚴重。
  2. 系統推薦當日志達到某個閾值時會做一次snapshot。如果snapshot頻率過低,否則一旦節點重啓需要回放大量日誌,影響可用性。
  3. 做一次snapshot可能耗時過長,會影響正常日誌條目的複製過程。我們可以通過copy on write寫時複製技術來避免snapshot過程影響正常的日誌條目複製過程。

帶寬消耗很嚴重。

  1. 系統推薦當日志達到某個閾值時會做一次snapshot。如果snapshot頻率過低,否則一旦節點重啓需要回放大量日誌,影響可用性。
  2. 做一次snapshot可能耗時過長,會影響正常日誌條目的複製過程。我們可以通過copy on write寫時複製技術來避免snapshot過程影響正常的日誌條目複製過程。

七、集羣成員變化

集羣成員變化是在集羣運行過程中副本發生變化,如替換那些宕機的節點或者增加/減少副本數。儘管可以通過暫停整個集羣以更新所有配置,然後重啓整個集羣的方式來實現,但是這樣會導致集羣在暫停期間不可用。因此Raft協議中考慮到了這個風險,將自動化配置納入到算法中來。
爲了讓配置修改機制能夠安全地運行,在轉換過程中不能夠存在任何時間點使得兩個leader在同一個任期中同時被選舉成功。但是如果將成員變更問題當成一般的一致性問題,直接向leader發送成員變更請求,leader複製成員變更日誌,達成majority後提交,各Follower提交成員變更日誌後從舊成員配置(Cold)切換到新成員配置(Cnew)。
因爲各個服務器提交成員變更日誌的時間可能不同,導致各個服務器節點從Cold轉換到Cnew的時刻不同。成員變更過程的某一時刻,在Cold和Cnew中同時存在兩個不相交的多數派,導致選出兩個leader,破壞了協議的安全性。
成員變更的某一時刻Cold和Cnew中同時存在兩個不相交的多數派
因此爲了保證安全性,成員配置變更必須使用兩階段方法。目前有很多種兩階段的實現。例如,有些系統在第一階段停掉舊的成員配置,所以集羣就不能處理外部的請求。在第二階段再啓用新的配置。
在Raft協議中,集羣先從舊成員配置Cold切換到一個過渡成員配置,稱爲共同一致(joint consensus),共同一致是舊成員配置Cold和新成員配置Cnew的並集Cold∪Cnew,一旦共同一致Cold∪Cnew被提交,系統再切換到新成員配置Cnew。
在這裏插入圖片描述
Raft協議兩階段成員變更過程如下:
1.Leader收到成員變更請求從Cold切換成Cnew;
2.Leader在本地節點生成一個新的log entry,內容爲Cold∪Cnew,代表當前時刻新舊拓撲配置共存,寫入本地日誌,同時將該log entry推送至其他Follower節點。在此之後新的日誌同步需要保證得到Cold和Cnew兩個多數派的確認;
3.Follower節點收到log entry後更新本地日誌,並且此時就以該配置作爲自己認知的全局拓撲結構;
4.如果Cold和Cnew中的兩個majority的Follower節點確認了Cold∪Cnew這條日誌的時候,Leader就提交這條log entry;
5.隨後Leader會生成一條新的log entry,內容是全新的成員配置Cnew,同樣將這條log entry寫入本地節點日誌,並同時推送到其他Follower節點上;
6.Follower收到新的配置日誌Cnew後,將其寫入日誌,並且從當前時刻起,將Cnew作爲系統成員拓撲結構,並且如果發現自己不在Cnew這個配置中會自動退出;
7.Leader收到majority的Follower節點的確認消息後,給客戶端發起命令執行成功的消息。後續的日誌只要得到Cnew多數派的確認即可。

在Raft兩階段成員配置變更的過程中,會出現一些異常現象:
1.新的服務器節點在初始化時沒有存儲任何的日誌條目。當這些服務器節點以這種狀態加入集羣中,那麼這些新加入的節點需要一定的時間來追趕日誌,這段時間內還無法提交新的日誌條目。爲了避免這種可用性的間隔時間,Raft在成員變更的時候使用了一種額外的階段,在這個階段,新的服務器節點以沒有投票權的身份加入到集羣中(Leader會複製日誌給他們,但是不考慮他們是大多數)。一旦新的服務器節點追趕上了集羣中的其他機器,就可以按照上面描述的做成員配置變更。
2.集羣的Leader不是Cnew中的一員。在這種情況下,Leader會在提交了Cnew日誌之後退出,回到Follower狀態。因此會有一段時間Leader管理着集羣,但是並不在集羣成員範圍內。Leader複製日誌但是不把他自己算作大多數之一。當Cnew被提交之後,會發生Leader過渡,因爲Cnew提交之後,最新的集羣成員配置就可以正常獨立工作了,此時會在Cnew範圍內選出新的Leader。在此之前,Leader都是從Cold範圍內選舉Leader。
3.移除不在Cnew中的服務器可能會擾亂集羣。這些服務器節點將不會再接收到Leader發來的心跳RPC。所以當選舉超時,他們就會重新發起leader election。他們會發送擁有最新term_id的RequestVoteRPCs,這樣會導致當前的Leader會回退成Follower狀態。新的Leader最終會被選舉出來,但是被移除的服務器將會再次超時,然後這個過程會再次重複,導致集羣整體可用性大幅降低。
因此爲了避免這個問題,當服務器節點確認當前Leader存在時,服務器節點會忽略RequestVoteRPCs。並且當服務器節點在當前最小選舉超時時間內收到一個RequestVoteRPC,他也不會更新當前的任期號或者投出選票。這不會影響正常的選舉,每個服務器在開始一次選舉之前,至少會等待一個最小選舉超時時間。
4.如果Leader的Cold∪Cnew尚未推送到Follower,Leader就掛了,此後新選舉的Leader並不包含這條日誌,此時新Leader依然使用Cold作爲自己的成員配置。
5.如果Leader的Cold∪Cnew推送到大部分的Follower後自己就宕機了,此後選出的新Leader可能是Cold也可能是Cnew中的某個Follower。
6.如果Leader在推送Cnew配置的過程中宕機了,後面新選出來的Leader可能是Cold也可能是Cnew中的某一個,此後客戶端繼續執行一次改變配置的命令即可。
7.如果大多數的Follower確認了Cnew這個消息後,即使Leader此時掛了,後面新選出來的Leader肯定位於Cnew集羣中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章