分佈式系統中主要的問題就是如何保持節點狀態的一致性,不論發生任何failure,只要集羣中大部分的節點能夠正常工做,則這些節點具備相同的狀態,保持一致,在client看來至關於一臺機器。html
一致性問題本質就是replicated state machines,即全部結點都從同一個state出發,都通過一樣的一些操做序列(log),最後到達一樣的state。其中保證各個節點執行相同的操做序列就是raft算法所要實現的。在raft算法中有一個Leader的角色,client與之進行交互,而且Leader協調Follower,保障全部的Follower具備相同的操做序列,最後提交這些操做,使狀態機達成一個新的一致的stat。java
整個raft算法分爲Leader選舉,日誌分發,日誌壓縮(快照),集羣成員變動。其中的Leader選舉是算法的核心部分。算法保證任什麼時候候最多隻有一個Leader,可是可能沒有Leader(好比正在選舉過程當中或者集羣成員多數不可用時)。在Leader確立以後,就能夠進行日誌分發,算法保證日誌會被安全的分發到集羣中而且應用到狀態機的日誌和本身相同。快照是爲了減小日誌量,去除中間過程。集羣成員變動是爲了在不停服務的狀況下安全使用新的集羣配置。算法
Raft在非拜占庭錯誤狀況下,包括網絡延遲、分區、丟包、冗餘和亂序等錯誤均可以保證正確,不會返回錯誤結果,這就是安全性保證。實際上就是保證全部成員狀態機都以一樣的順序,執行一樣的命令。下面分析幾種典型的場景下,raft是如何保證安全性的。安全
1. Leader選舉以後,若是Follower與Leader日誌有衝突該如何處理?服務器
Raft規定Follower中的衝突日誌會被Leader中的日誌覆蓋,使得Follower中的日誌老是與Leader的日誌保持一致。Leader必須找到Follower日誌中最後二者達成一致的地方,而後刪除從那個點以後的全部日誌條目,發送本身的日誌給Follower。全部的這些操做都在進行日誌複製RPC的一致性檢查時完成: Leader針對每個Follower維護了一個 nextIndex,表示下一個須要發送給Follower的日誌條目的index。當一個Leader剛得到權力的時候,他初始化全部的 nextIndex 值爲本身的最後一條日誌的index加1。Leader每次發送日誌複製RPC的時候會包含兩個值:prevLogIndex和prevLogTerm,分別對應緊隨新日誌條目以前日誌的索引值(index)和任期號(term),即prevLogIndex = newIndex - 1,若是Follower在它的日誌中找不到包含Leader發送過來的index和term的條目,那麼他就會拒絕接收新的日誌條目。也就是此時Follower的日誌和Leader不一致,日誌複製RPC 時的一致性檢查就會失敗。在被Follower拒絕以後,Leader就會減少 nextIndex 值並進行重試。最終 nextIndex 會在某個位置使得Leader和Follower的日誌達成一致。當這種狀況發生,日誌複製 RPC 就會成功,這時就會把Follower衝突的日誌條目所有刪除而且加上Leader的日誌。一旦日誌複製 RPC 成功,那麼Follower的日誌就會和Leader保持一致,而且在接下來的任期裏一直繼續保持。經過上述步驟,Raft算法保證了日誌是順序複製的。就是說,假若有一條舊的日誌還未複製給FollowerA,那麼更新的日誌就不能複製。日誌的順序複製,很大程度上簡化了Raft算法。好比查看各成員日誌的新舊,只要比較最後一條日誌便可。網絡
2. 若是在一個Follower宕機的時候Leader提交了若干的日誌條目,而後這個Follower上線後可能會被選舉爲Leader而且覆蓋這些日誌條目,如此就會產生不一致。分佈式
Raft經過對Leader的選舉進行限制,來保證所新選出的任何Leader對於給定的任期號,都擁有了以前任期的全部被提交的日誌條目,限制規則爲:candidate發送出去的投票請求消息必須帶上其最後一條日誌條目的Index與Term;接收者須要判斷該Index與Term至少與本地日誌的最後一條日誌條目同樣新,不然不給投票。由於 前一個Leader提交日誌條目的條件是日誌複製給集羣中的多數成員,Candidate選舉爲Leader的條件也是須要多數成員的投票。那麼這兩個大多數中成員一定有一個交叉,即有一個成員具備該日誌,而且投票給了新Leader,也就意味着新Leader的日誌至少不比該成員舊,那麼新Leader也具備該日誌。這樣就獲得證實了,後續的Leader必定具備前面Leader提交的日誌。spa
3. 即便保證上述選舉規則,也不能保證一致性,也就是說會出現Leader提交了前面任期的日誌條目以後,該條目還有可能被後來的Leader覆蓋而產生不一致。以下圖所示:.net
若是上述狀況(c)中,term=2,index=2的日誌條目被複制到大多數後,若是此時當選的S1提交了該日誌條目,則後續產生的term=3,index=2會覆蓋它,此時就可能會在同一個index位置前後提交一個不一樣的日誌,這就違反了狀態機安全性,產生不一致。也就是說當一個新Leader當選時,因爲全部成員的日誌進度不一樣,極可能須要繼續複製前面term的日誌條目,就算複製到多數服務器而且提交,仍是可能被覆蓋,由於前面term對應的日誌條目較舊,容易使的沒有這些條目的其餘服務器當選Leader,此時就會覆蓋這些日誌條目。日誌
爲了消除上述場景就規定Leader能夠複製前面任期的日誌,可是不會主動提交前面任期的日誌。而是經過提交當前任期的日誌,而間接地提交前面任期的日誌。
4.配置變動的時候,須要保證任什麼時候候只能有一個Leader,直接從舊的配置轉化到新配置的方案是不安全的,以下圖所示:
轉換過程當中,server1,server2經過舊配置選出一個Leader(三臺中的兩臺支持),server3,server4,server5經過新配置選出一個Leader(五臺中的三臺支持),這樣在同一個term中就有兩臺Leader,形成不一致,爲了保證安全性,配置更改必須使用兩階段方法。在 Raft 中,集羣先切換到一個過渡配置,咱們稱之爲共同一致;一旦共同一致已經被提交了,那麼系統就切換到新的配置上。
過渡配置保證不會在old與new兩個配置上同時產生Leader :
過渡配置是指由 old配置和 new複合成的一個新配置(old+new)。
Leader會將該過渡配置日誌先應用到本地,而後複製給集羣中的全部成員。全部收到配置的成員,會直接將配置應用到本地。
處於過渡配置的成員,在Leader選舉與提交日誌時規則發生了變化,要求分別獲得old與new兩個配置下的多數成員贊成才行。好比:
old:一、二、3
new:二、三、四、五、6 (刪除機器1,增長機器四、五、6)
old+new:一、二、三、四、五、6(機器是old+new全部機器)
那麼處於過渡配置的成員在Leader選舉與提交日誌時,須要獲得 old(一、二、3)三個成員中的多數支持,以及new(二、三、四、五、6)五個成員中的多數支持(而不是old+new中六個成員的多數)。
那麼上述過分配置如何保證不一樣時間段只產生一個Leader:
1)從old -> old+new配置提交以前
此時還未產生new配置,所以不可能在new配置下產生Leader。
那麼是否可能在old與old+new下分別產生Leader哪? old下要產生Leader須要old中的多數投票,old+new下要產生Leader也須要old中的多數投票,而要在old與old+new下分別產生Leader,則old中至少有一個成員同時投票給old與old+new中的Leader。根據投票規則,在一個term下只能投票一次,所以就不可能在old與old+new配置下在同一個term上分別產生Leader。
而在不一樣term下產生則不違反規則,由於新的term下產生的Leader,發送心跳給舊Leader,舊Leader就會立刻變成Follower。
2)old+new提交以後
只要old+new這個配置提交,則意味着該配置已經複製給了old中的多數成員,那麼在old配置中就不可能再產生一個Leader出來了。只能在old+new與new配置下產生。而在old+new與new中也不可能在同一個term下分別選舉出Leader,這個證實與old與old+new配置是同樣的。
若每次增長或者刪除一個成員不須要過渡配置:
就是說,每次配置變動只能增長或者刪除一個成員。若知足則能夠直接從old配置轉換到new配置。
證實:舊配置有N個成員,新配置有N+1個成員,在切換過程當中不會在舊配置與新配置下分別產生Leader。
1)若舊配置下選舉出Leader,那麼須要N/2+1個成員投票。
2)新配置下,從成員數是N+1去掉以及投票的N/2+1個成員只剩下N/2個成員。
3)而新配置下要選舉出Leader須要(N+1)/2+1=N/2+3/2個成員投票,可是隻剩下N/2個成員能夠投票,所以新配置下不可能再選舉出Leader。
4)反過來也同樣。
參考: http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432287.html