解讀Raft(三 安全性)
前言
之前的兩篇文章更多的是在描述Raft演算法的正常流程,沒有過多的去討論異常場景。
而實際在分散式系統中,我們更多的都是在應對網路不可用、機器故障等異常場景,所以本篇來討論一下Raft協議的安全性,即在異常場景下是否會導致資料丟失、資料不一致等情況。
選舉限制
在Raft協議中,所有的日誌條目都只會從Leader節點往Follower節點寫入,且Leader節點上的日誌只會增加,絕對不會刪除或者覆蓋。
這意味著Leader節點必須包含所有已經提交的日誌,即能被選舉為Leader的節點一定需要包含所有的已經提交的日誌。因為日誌只會從Leader向Follower傳輸,所以如果被選舉出的Leader缺少已經Commit的日誌,那麼這些已經提交的日誌就會丟失,顯然這是不符合要求的。
這就是Leader選舉的限制:能被選舉成為Leader的節點,一定包含了所有已經提交的日誌條目。
回看演算法基礎中的RequestVote RPC:
引數 | 解釋 |
---|---|
term | Candidate的任期 |
candidateId | Candidate的ID |
lastLogIndex | Candidate最後一條日誌的索引 |
lastLogTerm | Candidate最後一條日誌的任期 |
引數 | 解釋 |
---|---|
term | 當前任期,用於Candidate更新自己的任期 |
voteGranted | true表示給Candidate投票 |
請求中的lastLogIndex和lastLogTerm即用於保證Follower投票選出的Leader一定包含了已經被提交的所有日誌條目。
- Candidate需要收到超過版本的節點的選票來成為Leader
- 已經提交的日誌條目至少存在於超過半數的節點上
- 那麼這兩個集合一定存在交集(至少一個節點),且Follower只會投票給日誌條目比自己的“新”的Candidate,那麼被選出的節點的日誌一定包含了交集中的節點已經Commit的日誌
日誌比較規則(即上面“新”的含義):Raft 通過比較兩份日誌中最後一條日誌條目的索引值和任期號定義誰的日誌比較新。如果兩份日誌最後的條目的任期號不同,那麼任期號大的日誌更加新。如果兩份日誌最後的條目任期號相同,那麼日誌比較長的那個就更加新。
日誌提交限制
上圖按時間序列展示了Leader在提交日誌時可能會遇到的問題。
- 在 (a) 中,S1 是領導者,部分的複製了索引位置 2 的日誌條目。
- 在 (b) 中,S1 崩潰了,然後 S5 在任期 3 裡通過 S3、S4 和自己的選票贏得選舉,然後從客戶端接收了一條不一樣的日誌條目放在了索引 2 處。
- 然後到 (c),S5 又崩潰了;S1 重新啟動,選舉成功,開始複製日誌。在這時,來自任期 2 的那條日誌已經被複制到了叢集中的大多數機器上,但是還沒有被提交。
- 如果 S1 在 (d) 中又崩潰了,S5 可以重新被選舉成功(通過來自 S2,S3 和 S4 的選票),然後覆蓋了他們在索引 2 處的日誌。反之,如果在崩潰之前,S1 把自己主導的新任期裡產生的日誌條目複製到了大多數機器上,就如 (e) 中那樣,那麼在後面任期裡面這些新的日誌條目就會被提交(因為S5 就不可能選舉成功)。 這樣在同一時刻就同時保證了,之前的所有老的日誌條目就會被提交。
任期2內產生的日誌可能在(d)的情況下被覆蓋,所以在出現(c)的狀態下,Leader節點是不能commit任期2的日誌條目的,即不能更新commitIndex。
在上圖最終狀態是(e)的情況下,commitIndex的變化應該是1->3,即在(c)的情況下,任期4在索引3的位置commit了一條訊息,commitIndex直接被修改成3。
而任期2的那條日誌會通過Log Matching Property最終被複制到大多數節點企且被應用。
Raft演算法保證了以下特性:
- 如果兩個日誌條目有相同的index和term,那麼他們儲存了相同的指令(即index和term相同,那麼可定是同一條指令,就是同一個日誌條目)
- 如果不同的日誌中有兩個日誌條目,他們的index和term相同,那麼這個條目之前的所有日誌都相同
兩條規則合併起來的含義:兩個日誌LogA、LogB,如果LogA[i].index=Log[i]B.index且LogA[i].term=Log[i].term,那麼LogA[i]=Log[i]B,且對於任何n < i的日誌條目,LogA[n]=LogB[n]都成立。(這個結論顯而易見的可以從日誌複製規則中推匯出來)
歡迎關注公眾號交流: