# Raft 共識性算法 ## Introduction ### Why Consensus Algorithm Raft 是一個分布式的【共識】(Consensus) 算法,為何分布式系統需要共識? 這要回到最常見的容錯手段: 複製 (Replica)。要實現主從複製,最常使用的方法是複製狀態機 (Replicated State Machine),我們將所有節點都視為一個狀態機,且這個狀態機具備確定性,從同樣的初始狀態開始,經過相同的輸入序列後,會達到相同的狀態並輸出相同的結果。而複製狀態機一般透過複製日誌 (Replicated Log) 實現 牽扯到多副本複製,就會有一致性的問題,假如我們能透過某種算法,讓各個節點之間對日誌紀錄的操作與順序達成共識,每個節點都儲存相同的日誌副本,這樣集群中的每個節點,都有一致的狀態及輸出,那對客戶端來說,這個系統就像是一個具備高可用、自動容錯、高性能的單節點狀態機 ![image](https://hackmd.io/_uploads/r1LTu-RbC.png) ### Characteristics of Consensus Algorithm 適用於實際系統的共識算法通常有以下幾個特徵: - 確保在非拜占庭錯誤下的安全性,不會返回一個錯誤的結果 - 消息有可能延遲、丟失、亂序或重複 - 節點可能發生故障 - 網路可能發生分區 - 只要多數 (Majority) 節點能夠工作,彼此之間與客戶端之間能夠通信,那系統就是可用的;所以一個有 2*f + 1 節點的系統,允許 f 個不可達的節點 - 節點故障後,能夠透過持久化存儲恢復狀態,並重新加入集群 - 在保證日誌一致性上不依賴時間 (timing): 錯誤的時鐘和極端消息延遲會影響可用性 - 在一般情況下,只要集群中多數的節點響應一條命令,那這條命令就可視為完成。少數響應時間較長的機器不影響整體系統性能 ### Leader Based Raft 是基於領導者 (Leader) 的共識算法,且任何時刻集群中至多只能有一個 Leader。複製日誌完全由 Leader 所管理,任何客戶端請求都會先發到 Leader,由 Leader 將日誌複製到其他節點,並在符合安全條件時,再由 Leader 告知各節點可以將日誌的變更套用 (Apply) 在自身的狀態機。單領導者的設計大幅簡化了日誌的管理,也比無 Leader 的系統更加高效: - Leader 能決定新日誌擺放的位置,無需詢問其他節點 - 數據流只有單向,從 Leader 流向其他節點 - 在一般情況下,只需要一輪的 RPC 交換,就可以確認一個請求;對於 Leaderless 的系統,第一輪要先確認一個臨時 Leader,第二輪才開始確認請求 然而 Leader 有可能發生故障或網路分區,在這種情況下必需選出新的 Leader,要如何確保 Leader 切換後系統狀態的一致性,Raft 將共識問題分解成三個相對獨立的子問題,以下將一一展開討論 ## Raft Basic ### Server States 在展開細節前,我們需要先了解服務器可能身處的狀態。在 Raft 中,服務器於任意時刻只能處於以下三種狀態之一: - Leader,處理客戶端請求及日誌複製 (如果客戶端和 Follower 發出請求,Follower 會將請求重新定向至 Leader 上) - Follower,被動接收 RPC 的節點,響應來自 Leader or Candidate 的請求 - Candidate: 當節點發起選舉時,會切換到介於 Leader 及 Follower 之間的臨時狀態 在系統正常運作時,只會有一個 Leader,其餘節點都是 Follower,下圖是這些狀態之間的轉移關係 ![image](https://hackmd.io/_uploads/SkIjPGCb0.png) ### Terms Raft 將時間劃分成一個個任意長度的任期 (Term),每段任期從選舉開始算起,在選舉時會有一個或多個 Candidates 發起選舉請求,假如某個 Candidate 在選舉中勝出,它就會成為該任期的 Leader。在某些情況下,可能發生選舉失敗的情況,那這個任期就會以沒有 Leader 結束 ![image](https://hackmd.io/_uploads/rkku-7AWR.png) 每個節點都會持久化儲存當前的任期號 (currentTerm),任期號是單調遞增的,也因此任期在 Raft 中還扮演著邏輯時鐘 (Logical Clock) 的腳色,幫助系統辨別過期的狀態及日誌,節點之間通訊時會將任期號帶上: - 如果節點發現自己的任期號比其他節點要小,會馬上更新任期號 - 如果 Leader or Candidate 發現自己的任期號過期了,會馬上切換為 Follwer 狀態 - 如果節點接收了過期任期號的請求,會直接拒絕該次請求 ### Raft RPCs Raft 算法中服務器之間透過 RPC 進行溝通,一般的共識算法只需要兩種 RPC: 1. RequestVote => 由 Candidate 在選舉時發出 2. AppendEntries => 由 Leader 發出,用於日誌複製及心跳包 ### Raft Guarantees - Election Safety:在任何一個任期內,最多只會有一位領導人被選出來 - Leader Append-Only:領導人從不覆蓋或刪除其日誌中的記錄;只會進行追加 - Log Matching:如果兩個日誌包含完全相同的索引和任期的記錄,那麼從該索引開始往前的所有記錄也都是完全相同的 - Leader Completeness:如果某個記錄在某個任期被提交,那麼它將出現在所有任期大於該任期的領導人的日誌中 - State Machine Safety:如果一個節點在特定索引應用了一個記錄到它的狀態機,那麼其他節點不會在相同的索引應用另一個不同的記錄 ## Leader Election ### Election Process Raft 使用 Heartbeat 機制來觸發選舉 1. 當一個服務器啟動時,一開始都會是 Follower,只要在一段時間內 (Election Timeout,一般來說是 100ms ~ 500ms) 從 Candidate or Leader 接收到合法的 RPC 請求,就會保持在 Follower 狀態 2. Leader 定期發送心跳包 (空的 AppendEntries) 給所有 follower,以保持 Leader 身分 3. 如果 follower 在 election timeout 時間內沒有收到任何 RPC,會認定目前沒有有效的 Leader,並發起一次選舉 當 Follower 發起選舉時: 1. 先增加當前的任期號 2. 切換到 Candidate 狀態 3. 投自己一票,並向其他節點發送 RequestVote 請求,如果沒有收到指定節點的響應,會反覆嘗試直到發生以下三種狀況之一: - (a) 該 Follower 贏得選舉成為 Leader - (b) 收到來自 Leader 的 RPC,轉為 Follower (Raft Leader 贏得選舉時,不會直接跟其他節點說我是 Leader,而是透過心跳包隱式的告知) - \(c) 選舉超時,該任期沒有產生有效的 Leader,發起新一輪選舉 (a) 如果 Candidate 收到集群中多數節點對同一任期的選票,那 Candidate 就成為該任期的 Leader。對於同一個任期,每個節點只能投出一票 (先來先贏,不過為了確保日誌狀態的一致性,還是有些限制的,請參閱 Safety 章節)。Candidate 成為 Leader 後,會馬上向其他 follower 發送心跳包,告知選舉成功並避免新的選舉被觸發 (b) 在等待投票期間,Candidate 可能會從其他服務器收到 AppendEntries 請求聲稱自己是 Leader,如果這個 Leader 的任期: 1. \>= Candidate 的 currentTerm,那 Candidate 就承認該 Leader 是合法的,並回歸到 Follower 狀態 2. < Candidate 的 currentTerm,那就拒絕該請求,並維持在 Candidate 狀態 \(c) 如果同時有多個 Candidates 發起選舉,投票會很分散導致選舉失敗,這時每個 Candidate 都會因為超時而觸發另一次選舉流程,我們來看一個極端一點的例子,假如集群有 3 個節點同時發起選舉,由於每個節點都投給自己導致這次選舉失敗,假如我們不夠幸運的話,3 個節點的選舉定時器又在同一時間到期,又選不出 Leader 導致系統無法前進,這樣的問題稱為選票分割 (Split Vote) ### How to Avoid Split Vote Raft 不能完全避免選票分割的問題,但可以透過一些機制大幅降低發生的機率: 1. 讓節點可以在足夠大的時間範圍內,隨機選擇一個超時時間。這樣可以讓節點間的超時時間較為分散,在大多數情況下只會有一台超時,而且這台超時的節點會在其他節點超時前贏得選舉 (盡可能讓不同節點超時時間的差異至少大於一次 RPC 的 round-trip time) 2. 每個 Candidate 在開始選舉前,隨機重置其 election timeout,減少新一輪選舉也發生投票分裂的可能性 ![image](https://hackmd.io/_uploads/HyOIAQCZA.png) ## Log Replication ### Log Organication 日誌的結構如下圖所示,每個 Entry 包含: - Index,用來標示在日誌中位置的整數 - Term,Leader 添加 Entry 時的任期號 - Command,狀態機指令 ![image](https://hackmd.io/_uploads/B19iUB0WA.png) ### Replication Process Leader 被選舉出來後,就開始為客戶端提供服務 - 客戶端請求都包含一條將被複製狀態機執行的命令 - Leader 將該請求追加 (Append) 到自己的日誌中 (要寫入持久化存儲中),然後透過 AppendEntries RPC 併發地複製給其他節點 - Followers 收到請求,將 Entry 寫入持久化存儲後回覆請求 - Leader 收到多數節點的響應,則認定該 Entry 是 committed,推進 commitIndex,然後將命令套用至自身的狀態機,再推進 applyIndex,並將執行結果返回給客戶端 - Leader 知道 Entry 被 committed 後,會在後續的 AppendEntries RPC 中,告知 Followers - Followers 收到通知後,將 committed Entries 的命令套用在自己的狀態機 - 如果 Follower 故障或網路速度很慢,無論 Leader 是否已經響應客戶端,Leader 都會無限重試 AppendEntries 請求,直到所有 Follower 儲存了所有的 Log Entries ### Log Matching 那一個 Entry 被 committed 在 Raft 中代表什麼涵意呢? - 這個 Entry 被多數節點持久化儲存 - 這個 Entry 之前所有 Leader 的 Log Entries 都是 committed 的 Raft 設計這種日誌機制簡化了系統的行為,也讓系統更容易預測,並盡可能的保持各節點日誌的高度一致性 (Coherency),這個設計也是保證安全性的重要機制 Raft 會一直維護 Log Matching 的特性,如果兩個日誌副本中的兩個 Entries 有完全相同的 Index 與 Term: 1. 那這兩個 Entries 一定有相同的命令;這來自以下事實:Leader 在給定 Index 與 Term 的情況下,最多只會追加一個 Entry,而且其在日誌的位置不會變更 2. 這兩個日誌中,該 Index 之前的所有 Entries 都是相同的;這是透過 AppendEntries 中的一致性檢查來確保,在稍後詳述 在正常情況下,Leader 跟 Followers 的日誌能保持一致,但 Leader 如果發生故障 or 分區,有可能導致日誌副本間的不一致狀態 (例如 Leader 將 Entry 複製給所有節點前就掛了),這會導致一些複雜的情況,如下圖的範例 ![image](https://hackmd.io/_uploads/HJ-FM8RZA.png) 圖中每個方框代表一個 Entry,其中的數字代表 Term: - 紀錄丟失 (a-b) - 有額外未提交的紀錄 (c-d) - 兩者皆有 (e-f) 以 (f) 為例,一個可能的情況是在 Term = 2 時成為 Leader,向自己的日誌添加一些 Entries 後,在 commit 前就故障了;重啟後又馬上成為 Term = 3 的 Leader,又往自己的日誌添加一些 Entries,然後在 commit 前又故障了,並且持續了好幾個 Term ### AppendEntries Consistency Check 為了使 Followers 跟 Leader 的日誌一致,在 Raft 中會強迫 Followers 複製 Leader 的日誌來解決衝突,Followers 日誌中跟 Leader 衝突的部分會被覆蓋,整個過程都發生在 AppendEntries RPC 的一致性檢查過程中,流程大致如下: 1. 找到 Leader 和 Follower 最後一個一致的 Entry 2. 將 Follower 之後的 Entries 全部刪除 3. Leaders 將之後的所有 Entries 發送給 Follower 具體的方法是: - Leader 維護所有 Followers 的 nextIndex,代表 Leader 要發送 Entries 給 Followers 的索引起點,當選出新 Leader 時,會初始化每個 Follower 的 nextIndex 為自己日誌的下一個 Index - Leader 發出 AppendEntries 請求時,會帶上 prevTerm & prevIndex,當然這裡的 prev 是相對該 Follower nextIndex 的前一個 Entry - Follower 收到請求後會比對 prevIndex 及 prevTerm,若與 Leader 日誌不一致,一致性檢查會失敗進而拒絕請求,Leader 收到拒絕的回應後,會減小該 Follower 的 nextIndex 並重試 AppendEntries,這個過程會一直不斷重複直到 Follower 一致性檢查成功並將衝突部分覆蓋為止,此時 Leader 跟 Follower 的日誌狀態達到一致 ### Fast Log Backup 上述流程的問題是,假如某個 Follower 落後非常多,例如 1000 個 Entries,那,所以論文有提到可以進一步優化,讓 nextIndex 快速收斂,不過論文中並沒有非常明確的講明作法,這邊參考 Morris 教授講課時提出的策略進行說明 這裡的思想是,讓 Follower 返回足夠的資訊給 Leader,讓 Leader 能夠以任期為單位來回退,這裡的額外資訊有 3 個: 1. XTerm: Follower 與 Leader 衝突 Entry 對應的任期號,如果 Follower 在 prevIndex 對應位置的任期號不匹配,會拒絕請求並將自身的任期號放在 XTerm 中;如果對應位置沒有 Entry 則回傳 -1 3. XIndex: Follower 對應任期 XTerm 第一條 Entry 的 Index 4. XLen: Follower 日誌的長度 Morris 教授將可能出現的場景分為三類,為了簡化系統只有一個 Leader S2 與 Follower S1,S2 要發送一個任期號為 6 的 AppendEntries 給 S1 - 場景 1: S1 沒有任何 Term = 6 的 Entry,需要回退一整個任期 - XTerm = 5,XIndex = 2,Leader 發現自己沒有任期 5 的紀錄,所以會將 nextIndex[S1] 設為 XIndex,所以如果 Leader 完全沒有 XTerm 的任何紀錄,就應該直接回退到 XIndex ![image](https://hackmd.io/_uploads/SyCLgOAbC.png) - 場景 2: S1 有任期 4 的多個舊 Entries,但 S2 只收到一條,所以需要覆蓋 S1 一些舊 Leader 的 Entries - XTerm = 4,XIndex = 1,Leader 發現自己有任期 4 的紀錄,因此將 nextIndex[S1] 設置為 2,也就是 XTerm 最後一個 Entry 的下一個位置 ![image](https://hackmd.io/_uploads/HJccguCZR.png) - 場景 3: S1 與 S2 的日誌並沒有衝突,但 S1 缺失了部分 S2 的 Entries - XTerm = -1,XLen = 1,代表 Follower 的日誌太短了,以至於在衝突發生的位置沒有紀錄,Leader 應退回到 Follower 最後一條紀錄的下一個位置 ![image](https://hackmd.io/_uploads/SJZ5NO0bR.png) 無論是否採用優化,這個檢測機制讓新的 Leader 上台後,無需執行任何特殊操作來恢復日誌的一致性,由於 Leader 永遠不會覆蓋或刪除自己的 Entries,只要正常操作,日誌副本最終會透過 AppendEntries 一致性檢查來收斂 ### Follower and Candidate Crashes Follower 和 Candidate 故障的情況,處理起來非常單純,Raft 會無限重試: - 因為只要節點會從故障中恢復,RPC 就一定會完成 - 如果一個節點在完成 RPC 後,響應前發生故障,由於 Raft RPC 是冪等的,所以不會有任何問題。例如,如果一個 Follower 收到一個 AppendEntries 請求,但它的日誌已經包含了該紀錄,那就會直接忽略這個請求 ### Timing and Availability Raft 一個設計要求是安全性不能依賴時序: 不能因為某些事件發生的快或慢了就產生錯誤的結果。但可用性 (系統即時對客戶端做出響應) 對時序的依賴是無可避免的,例如訊息的交換時間比節點崩潰持續的時間還要長,那任何一個 Candidate 可能都無法等到一個選舉結果,而缺少了穩定的 Leader,Raft 將無法正常工作 Raft 中最依賴時序的部分就是選舉,只要系統滿足以下時序條件,Raft 就能夠選出並保持一個穩定的 Leader ```broadcastTime ≪ electionTimeout ≪ MTBF``` - broadcastTime: 節點間通信平均的 round-trip time - electionTimeout: 選舉超時時間 - MTBF: 單個節點的平均故障時間 這個不等式也很好理解: 1. 廣播耗時必需低於選舉超時一個數量級,確保 Leader 能透過心跳包維持身分,加上隨機選取的選舉超時,使得 Split Vote 幾乎不可能發生 2. 選舉超時必需小於 MTBF 幾個數量級,系統才能穩定運行。Leader 崩潰後,大約有一個選舉超時時間不可用,我們希望這個情況只占整個系統運行時間的一小部分 broadcastTime 和 MTBF 都是底層系統特性,electionTimeout 是我們需要設置的 - Raft 要求將請求持久化,因此取決於存儲技術,broadcastTime 約為 0.5 ms ~ 20ms - 承上,electionTimeout 一般選擇 10ms ~ 500ms - 典型的 MTBF 是數個月甚至更長的時間,很容易滿足不等式 ## Safety 前面介紹了 Raft 的選舉及複製機制,但這些機制還不足以確保每個狀態機會以相同的順序執行相同的命令,例如以下出錯的例子: ![image](https://hackmd.io/_uploads/rJehSGyMA.png) 1. S1 在 term=6 & term=7 成為 Leader 並追加了一些紀錄,但都在發出 AppendEntries 前就故障了 2. S2 在 term=8 成為 Leader 並追加了一個紀錄,這次 S3 有成功收到 AppendEntries 3. S2 故障,S1 在 term=9 再度成為 Leader 此時,我們可以強硬地讓 S2 & S3 複製 S1 的日誌嗎? 答案是不行的,因為我們無法得知在 index=2 的紀錄是否已經 commit & 響應客戶端。為了解決這個問題,Raft 在 Leader Election 上加上了一個限制條件,而這條限制確保了 Leader Completeness 特性 ### Election Restriction 在任何 Leader Based 的共識算法中,Leader 最終都必需儲存所有已提交的 Entries,Raft 採用一種非常簡單的方式來達成: ==除非前面所有 term 內已提交的 Entries 都已經在該節點上,否則該節點不能被選為 Leader==。這代表無需從 Non-Leader 節點向 Leader 節點同步資料,確保資料永遠只會從 Leader 到 Follower 單向流動 - 參與選舉的節點中,由於 Majority Vote,至少有一個節點有集群所有已提交的紀錄 - 只要有一個 Candidate 的日誌與多數節點相比==至少不落後== (at least as up-to-date),那它就持有集群的所有已提交紀錄 那要如何確保至少不落後語意呢? Raft 在 RequestVote 會確認這件事情,依據日誌最後一個 Term & Index,來判斷哪份日誌是更新的: - 如果 Term 不同,那 Term 更新的節點勝出 - 如果 Term 相同,那 Index 更大的節點勝出 用前面的例子當範例,當要選出 term=9 的 Leader 時,由於 S1 的 Term 比 S2 & S3 都低,所以 S1 不可能被選為 Leader;而 S2 or S3 為 Candidate 時,所有其他節點都可能投票給它們 ### Committing Entries from Previous Terms :::warning :notes: 這一節是以錯誤的情況來討論,為何 Raft 要限制 Leader 只能透過提交自己任期的 Entries,進而間接提交之前任期的 Entries ::: 那新的 Leader 該如何提交之前任期副本數量過半的 Entries? 先考慮錯誤的情況 (允許 Leader 可以複製之前任期的紀錄),即使某個項目已經存儲在大多數節點上,它仍然可能被新的領導者覆蓋: ![image](https://hackmd.io/_uploads/HyAq0MJG0.png) - (a):S1 是當前的 Leader,並將 index=2 的紀錄複製到 S2 後故障 - (b):S5 被 S3/S4/S5 選為 term=3 的 Leader,然後在 index=2 追加了新的紀錄後故障 - \(c):S1 被選為 term=4 的 Leader,然後 S1 繼續同步上一次未成功的紀錄(index=2),它被成功同步到大部分節點上(S1/S2/S3),但還未提交 接下來有兩種情況: - (d):S1 再次故障,S5 被 S2/S3/S4 選為 term=5 的 Leader,在這種情況下,index=2, term=2 的項目會被 index=2, term=3 的項目覆蓋。 - (e):S1 在失敗之前將 index=3, term=4 的一個新記錄複製到大多數節點上,這種情況下,即使 S1 之後故障了,S5 也無法贏得選舉。因此,index=3, term=4 的項目將會被(無論是 S1 還是其他新的 Leader)提交,日誌中所有之前的記錄也都會被提交。這是 S1 不發生故障,正確複製的情況 為了解決 (d) 的窘境,Raft 加上了另一條約束: ==Leader 只能提交自己任期的紀錄==,我們來看一下加上約束後從 \(c) 開始會如何影響提交流程: - \(c) 如果 S1 將 term = 4 的日誌複製到多數派,那麼 Leader 就可以提交,index=2 & term=2 也會間接一起提交,就是 (e) 的情況 - (d) 如果 S1 只將 term=4 寫入自己的日誌,然後故障了,S5 選舉成功成為 Leader,由於 S5 的 currentTerm 至少是 5,而 Leader 不能提交之前任期的紀錄,所以 term=3 的紀錄是不能提交的。只有等到新的請求進來,超過半數節點複製了 1-3-5 後,term=3 的日誌才能跟著 term=5 的一起提交。 ### No-op 雖然加了這個約束就不會重複提交了,但如果一直沒有新的請求進來,index=2 & term=3 不就一直不能提交?假設 \(c) 或 (d) 中 index=2 的命令是 Set("k", "1"),S5 當選 Leader 後客戶端來查詢 Get("k"),由於紀錄尚未提交,線性一致性又要求不能返回舊的數據,那這裡不就阻塞了? 因此 Raft 提出 no-op 紀錄來解決這個問題。本質上,no-op 紀錄的目的是讓 Leader 能夠快速提交之前任期未提交的紀錄,no-op 紀錄只包含 Index 和 Term 信息,不包含具體指令: - Leader 剛選舉成功時,立即追加一條 no-op 紀錄,並立即複製到其他節點 - 一旦 no-op 紀錄被提交,Leader 前面所有未提交的紀錄都會被間接提交,Leader 就能夠迅速回應客戶端的查詢 ### Safety Argument 有了前面的限制條件,接下來我們用反證法來證明 Leader Completeness 特性 ![image](https://hackmd.io/_uploads/SkuycQ1G0.png) 假設在任期 T 內的 Leader LeaderT,在其任期內提交了一個紀錄,但這個紀錄沒有被後面任期內的 Leader 存儲。考慮沒有存儲這個紀錄的最小的任期 U,其中 U > T - 當 LeaderU 在其任期內被選為 Leader 時,這個紀錄一定不在其日誌中,因為 Leader 不會刪除或覆蓋任何紀錄; - LeaderT 已經將這個紀錄同步到大部分節點上,而 LeaderU 又收到了多數節點的投票,代表至少有一個節點 V 既接受了 LeaderT 複製過來的記錄,又投票給 LeaderU - V 一定是在投票給 LeaderU 之前接受了這個紀錄,否則由於 U > T,它會拒絕那次來自 LeaderT 的請求 - V 將選票給了 LeaderU,代表 LeaderU 的日誌至少與 V 是一樣新的。這將導致以下兩個矛盾: 1. 如果 V 和 LeaderU 最後一個紀錄的任期相同,代表 LeaderU 的日誌至少與 V 的一樣長,否則拿不到選票,因此 LeaderU 的日誌一定包含 V 日誌中的所有紀錄,而前面我們假設 LeaderU 是不包含的,這就產生了矛盾 2. 如果 LeaderU 最後一個紀錄的任期號大於 V,由於 V 擁有在任期號為 T 提交的紀錄,所以 V 最後一個紀錄的任期號至少為 T,代表 LeaderU 之前的 某個 Leader 一定包含該紀錄,根據 Log Matching 特性,LeaderU 一定也包含了該已被提交的紀錄,這同樣產生了矛盾 透過 Leader Completeness 我們可以進一步證明 State Machine Safety: - 當一個節點將一個紀錄應用到自己的狀態機時,它的日誌和 Leader 的日誌從開始到該紀錄前都是相同的 - 考慮一個最小的任期號,在該任期中任意節點應用了一個給定索引的紀錄,Leader Completeness 保證該任期之後的所有 Leader 在該索引上會存儲相同的紀錄,代表在後續的任期中,該索引上的值永遠保持相同,State Machine Safety 得到保證 最後,透過 State Machine Safety 以及 Raft 要求以 Index 的順序應用紀錄,代表我們可以保證所有節點都會按照相同的順序及相同的內容,將 Entries 應用到自身的狀態機上,確保了副本間的一致性 ## Cluster Membership Changes `TODO` ## Log Compaction `TODO` ## Reference 1. [In Search of an Understandable Consensus Algorithm](https://www.usenix.org/conference/atc14/technical-sessions/presentation/ongaro) 2. [Lecture 06 - Raft1](https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/lecture-06-raft1) 3. [条分缕析 Raft 算法](https://mp.weixin.qq.com/s?__biz=MzIwODA2NjIxOA==&mid=2247484140&idx=1&sn=37876b5dda5294ea7f6211f0a3300ea5&chksm=97098129a07e083fe65f8b87c2ec516b630a8f210961038f0091fbcd69468b41edbe193891ee&cur_album_id=1538679041988345857&scene=189#wechat_redirect) 4. [[译] [论文] Raft 共识算法(及 etcd/raft 源码解析)(USENIX, 2014 )](https://arthurchiao.art/blog/raft-paper-zh/) 5. [【译文】Raft协议:In Search of an Understandable Consensus Algorithm (Extended Version) 大名鼎鼎的分布式共识算法](https://zhuanlan.zhihu.com/p/524885008)