摘要
raft是一種比paxos容易理解的一致性算法,實現起來比paxos簡單許多。本文前部分描述算法的細節,後部分嘗試探討下該算法的原理。算法
算法描述
raft算法之因此簡單的緣由之一是它將問題分解成三個子問題,分別是:安全
- Leader選舉
- Log複製
- 安全性保證
概述
raft協議中每一個server都要維護一些狀態,而且對外提供兩個RPC調用分別是RequestVote RPC和AppendEntries RPC用於選舉和log複製。
要想理解raft,其實就是搞明白:app
- leader和follower須要維護哪些變量,每一個變量的含義
- leader何時發送AppendEntries RPC,攜帶哪些參數,follower收到請求後作什麼?leader收到響應後作什麼?
- candidate何時發送RequestVote RPC,攜帶哪些參數,follower收到請求後作什麼?candidate收到響應後作什麼?
狀態:
RequestVote RPC
AppendEntries RPC
raft全部的操做都是爲了保證以下這些性質。ide
raft保證的性質
- Election Safety: at most one leader can be elected in agiven term.
- Leader Append-Only: a leader never overwrites or deletes entries in its log; it only appends new entries.
- Log Matching: if two logs contain an entry with the same index and term, then the logs are identical in all entries up through the given index.
- Leader Completeness: if a log entry is committed in agiven term, then that entry will be present in the logs of the leaders for all higher-numbered terms.
- State Machine Safety: if a server has applied a log entry at a given index to its state machine, no other server will ever apply a different log entry for the same index.
暫時能夠先不看,須要知道的是raft全部的規則都是爲了保證上面的這些性質,而這些性質又是保證raft正確的前提。設計
Leader選舉
Election Safety性質說的在某個term中最多隻能選出一個leader。
有以下這些規則:日誌
- raft將時間劃分爲term,每一個term都有一個number,每一個term以選舉一個leader開始。
- 每一個server有三種狀態:leader, follower, candidate。
- 做爲follower:有一個稱爲election timeout的倒計時,若是在倒計時內沒有收到有效的AppendEntries RPC,將轉換爲candidate,增長本身的term number,投本身一票,而後經過RequestVote RPC通知集羣中的其餘server進行投票。當半數以上的RequestVote RPC返回true後,這個candidate將轉換爲leader。
- 某個server收到RequestVote RPC後如何肯定要不要投贊同票?同時知足如下三個條件則投贊同,RequestVote RPC返回成功:
- 在當前週期內尚未投過票
- candidate中term不小於本身的term
- "選舉限制":想要得到投票,candidate的logs必須比當前follower的logs更up-to-date。如何比較兩個logs的up-to-date程度?最後一個log entry的term大的更up-to-date, 若是term同樣,index越大越up-to-date。(論文5.4.1節)
- 做爲leader:週期性的發送AppendEntries RPC。
Log replication
Entry格式
Logs由Entries組成,每一個Entry包含一個term和命令,格式以下:
Entry若是已經肯定能夠安全apply到狀態機的狀況下,將被標誌爲commited。看上面state圖中,每一個server都維護兩個變量commitIndex和lastApplied。這兩個變量都是初始化爲0,好比Figure 6中leader的commitIndex就是6,這個值會在
AppendEntries RPC中以leaderCommit參數通知followers修改本身的commitIndex。server
commitIndex和lastApplied有什麼區別呢?lastApplied老是<=commitIndex,commitIndex代表的是到commitIndex爲止能夠被apply,lastApplied代表的是到lastApplied爲止已經被apply。blog
什麼狀況下Entry能夠被commit?知足如下兩個條件:ci
- A log entry is committed once the leader that created the entry has replicated it on a majority of the servers.(leader將該entry拷貝到大部分server中)
- 不能commit term比當前leader的term小的Entry。這裏不是很好理解,論文在5.4.2節給出瞭解釋。
以下圖:
(a):S1是leader(term=2),entry 2只拷貝到了S2就奔潰了。
(b):S5成爲新的leader(term=3),而且接收了entry 3,可是還沒進行拷貝也崩潰了。
(c):S1從新成爲leader(只是term=4),而且將entry 2拷貝到了S3。若是沒有條件2的限制,只看條件1,Entry 2已經被複制到了大部分的server中,就能夠被commit了。那麼問題來了,若是Entry 2被commit後S1又奔潰了,這時S5從新成爲leader(根據上文給出的選舉規則,S5最後一個Entry的term是3,能夠得到S2, S3, S4和本身的投票,因此能夠成爲leader),並將Entry 3拷貝到其它的server(狀況(d)),而且commit,這樣以前commit的Entry 2就被覆蓋了,這是絕對不容許的,已經被commit的Entry不能被覆蓋。再次回到狀況(c),這時若是S1不只複製了Entry 2還複製了Entry 4(term=4)(狀況(e)),這種狀況下同時知足條件1和條件2,因此能夠commit Entry 2和Entry 4,由於和以前不一樣的是,若是S1如今奔潰了,S5不可能成爲leader(S5的最後一個Entry的term=3,S1, S2, S3都會拒絕投票,由於它們的logs更up-to-date),也就不可能出現commit的Entry被覆蓋的狀況。
這個圖畫的有點歧義,我總結下,(c)若是不考慮條件2的限制,可能會出現(d),(d)是不容許出現的。(c)若是同時考慮條件1和條件2,那麼可能出現(e),(e)是合法的,毫不可能出現(d)這種狀況。因此條件2是必要的。rpc
日誌拷貝的過程
- leader接收客戶端的Entry,將Entry添加本身的logs中。
- leader週期性使用AppendEntries RPC將新的entry備份到其它server。
- follower收到AppendEntries RPC後作什麼?進行一致性檢查。根據參數中的prevLogIndex,檢查本身的log的prevLogIndex處的Entry的term和參數中的prevLogTerm是否相同,若是相同則將參數中的entries拷貝到本身的log中,不然返回false,leader收到響應後若是發現是false,調整參數而後從新發送AppendEntries RPC。至於怎麼調整參數見論文5.3節。
不嚴格的正確性解釋
論文中指出raft正確性已經使用TLA+ specification language進行了證實,順便搜了下這個TLA+ specification language,由Leslie Lamport發明,用來驗證設計的正確性的語言,這個Leslie Lamport也是NB的一塌糊塗,Paxos算法也是他設計出來的。我沒有仔細研究嚴格的證實,只是以一種不嚴格的方式試圖解釋下爲何raft能夠保證一致性。
raft中全部的log都是由leader流向follower的,因此你leaader首先得保證擁有全部的committed log吧,這就是Leader Completeness屬性。那麼如何保證Leader Completeness屬性呢。我認爲如下這些規則保證了該屬性:
- Entry須要被大部分server接收才能被commit。
- leader須要大部分server投同意票才能成爲leader。
- "選舉限制":想要得到投票,candidate的logs必須比當前follower的logs更up-to-date。
能夠用反證法來證實:
假設Leader Completeness屬性不成立,term T的leader(T) commit了一個Entry,可是新的term U的leader(U)沒有包含該Entry。
leader(T)已經commit了該Entry,因此該Entry確定被大部分server接受了,leader(T)成爲leader確定收到了大部分server的投票,那麼一定存在一個server既接受了該這個Entry也投了leader(T)一票。顯然該server包含的log比leader(T)的要up-to-date,因此和規則3矛盾,因此假設不成立,Leader Completeness屬性成立。
保證了源頭log的正確性後,拷貝過程當中也要保證和leader的log一致。Log Matching屬性保證了這一點,該屬性描述的是:若是兩個server中logs中的某個index對應的log entry的term相同,那麼這個index以及以前對應的log entry都應該保證同樣。一致性檢查規則能夠保證該屬性。
raft這些性質中最重要的就是保證State Machine Safety屬性,該屬性描述的是任何一個server在某個index apply一個Entry,其它server不會在該index處apply一個不一樣的Entry。Leader Completeness + Log Matching能夠推出State Machine Safety。