一致性协议raft详解(三):raft中的消息类型
一致性協議raft詳解(三):raft中的消息類型
- 前言
- raft 節點
- Raft中RPC的種類
- RequestVote
- leader選舉成功后
- AppendEntries
- 請求參數
- 返回值
- 存儲日志(日志同步過程)
- InstallSnapshot RPC
- 快照的并發性
- 快照實現以及何時做快照
- 快照實現
- disk-based
- memory-based
- 參考鏈接
前言
有關一致性協議的資料網上有很多,當然錯誤也有很多。筆者在學習的過程中走了不少彎路。現在回過頭來看,最好的學習資料就是Leslie Lamport和Diego Ongaro的數篇論文、Ongaro在youtube上發的三個視頻講解,以及何登成的ppt。
本系列文章是只是筆者在學習一致性協議過程中的摘抄和總結,有疏漏之處敬請諒解,歡迎討論。
raft 節點
Raft算法中服務器有三種角色
每個服務器上都會存儲的持久狀態:
每個服務器上都會存儲的易失狀態:
上面兩個index只是索引,可能會有空擋,比如某個log entry沒有commit上
在狀態為Leader的服務器上會額外存儲的易失狀態:
Raft中RPC的種類
RequestVote
candidate節點請求其他節點投票給自己
請求參數:
返回值:
一個節點(無論當前是什么狀態)在接收到RequestVote(term, candidateId, lastLogIndex, lastLogTerm)消息時, 其會做如下判斷:
leader選舉成功后
領導人:
- 一旦成為領導人:發送空的附加日志 RPC(心跳)給其他所有的服務器;在一定的空余時間之后不停的重復發送,以阻止follower超時(5.2 節)
- 如果接收到來自客戶端的請求:附加條目到本地日志中,在條目被應用到狀態機后響應客戶端(5.3 節)
- 如果對于一個follower,如果leader發現自己的最后日志條目的索引值大于等于 nextIndex,那么:發送從 nextIndex 開始的所有日志條目:
- 如果成功:更新相應follower的 nextIndex 和 matchIndex
- 如果因為日志不一致而失敗,減少 nextIndex 重試
- 如果存在一個滿足N > commitIndex的 N,并且大多數的matchIndex[i] ≥ N成立,并且log[N].term == currentTerm成立,那么令 commitIndex 等于這個 N (5.3 和 5.4 節) (figure 8),這樣的話,leader就可以把漏下的日志補上
- 之所以這么做,是因為在新的leader選舉的過程中,老的leader是可以繼續生效的,那么也就導致新的leader可能確實了一部分老leader最后commit的日志,或者network partition了,某個節點的term很大,導致其一定是主,但是這個主上有很多漏掉的leader
AppendEntries
leader節點使用該消息向其他節點同步日志, 或者發送空消息作為心跳包以維持leader的統治地位
請求參數
返回值
一個節點(無論當前是什么狀態)接收到AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries[], leaderCommit)消息時, 其會做如下判斷(條件從上往下依次判斷):
存儲日志(日志同步過程)
leader會將commit index置為0 --> 大部分follower將commitindex推進之后 --> leader才會推進自己的commit index --> leader代表整個系統推進commit index
InstallSnapshot RPC
該rpc主要用于leader將集群的快照同步給其他節點。這里主要講一下快照的機制:
本節主要參考文章條分縷析 Raft 算法(續):日志壓縮和性能優化
log過多就需要做快照,最初設計 LogCabin 的時候沒有考慮日志壓縮,因此代碼上假定了如果 entry i 在日志中,那么 entry 1 到 i - 1 也一定在日志中。有了日志壓縮,這就不再成立了,前面的 entry 可能已經被丟棄了。
和配置變化不同,不同的系統有不同的日志壓縮方式,取決于你的性能考量,以及基于硬盤還是基于內存。日志壓縮的大部分責任都落在狀態機上。
不同的壓縮方法有幾個核心的共同點:
memory-based 狀態機的快照的大部分工作是序列化內存中的數據結構。
快照的并發性
創建一個快照需要耗費很長時間,包括序列化和寫入磁盤。**因此,序列化和寫快照都要與常規操作并發進行,避免服務不可用。**copy-on-write 技術允許進行新的更新而不影響寫快照。有兩個方法來實現:
- 狀態機可以用不可變的(immutable)數據結構來實現。因為狀態機命令不會 in-place 的方式來修改狀態(通常使用追加的方式),快照任務可以引用之前狀態的并把狀態一致地寫入到快照。
- 另外,也可以使用操作系統的 copy-on-write。例如,在 Linux 上可以使用 fork 來復制父進程的整個地址空間,然后子進程就可以把狀態機的狀態寫出并退出,整個過程中父進程都可以持續地提供服務。LogCabin中當前使用的就是這種方法。
快照實現以及何時做快照
服務器需要決定什么時候做快照。太過頻繁地做快照,將會浪費磁盤帶寬和其他資源;太不頻繁地做快照,則有存儲空間耗盡的風險,并且重啟服務需要更長的重放日志時間。
**一個簡單的策略是設置一個閾值,當日志大小超過閾值則做快照。**然而,這會導致對于小型狀態機時有著不必要的大日志。
一個更好的方法是引入快照大小和日志大小的對比,如果日志超過快照好幾倍,可能就需要做快照。但是在做快照之前計算快照的大小是困難并且繁重的,會引入額外負擔。所以使用前一個快照的大小是比較合理的行為,一旦日志大小超過之前的快照的大小乘以擴展因子(expansion factor),服務器就做快照。
這個擴展因子權衡空間和帶寬利用率。例如,擴展因子為 4 的話會有 20% 的帶寬用于快照(每1byte 的快照寫入有對應的 4bytes 的 log 寫入)和大約 6 倍的硬盤空間使用(舊的快照+日志+新的快照)。
快照仍然會導致 CPU 和磁盤的占用率突發,可以增加額外的磁盤來減輕該現象。
**同時,可以通過調度使得做快照對客戶端請求沒有影響。**服務器需要協調保證在某一時刻集群只有小部分成員集同時在做快照。由于 Raft 是多數派成員構成的 commit,所以這樣就不會影響請求的提交了。當 Leader 想做快照的時候,首先要先下臺,讓其他服務器選出另一個 Leader 接替工作。如果這個方法充分地可行,就可能消除快照的并發,服務器在快照期間其實是不可用的(這可能會造成集群的容錯能力降低的問題)。這是一個令人興奮的提升集群性能并降低實現機制的機會。(這里其實可以通過實現指定服務器做快照來優化,braft 里就有提到這點。)
快照實現
根據log的實現方式不同(分為memory-based和disk-based),快照也有不同的實現方式
disk-based
對于幾十或上百 GB 的狀態機,需要使用磁盤作為主要存儲。對于每一條記錄,當其被提交并應用到狀態機后,其實就可以被丟棄了,因為磁盤已經持久化存儲了,可以理解為每條日志就做了一個快照。
Disk-based 狀態機的主要問題是,磁盤會導致性能不佳。在沒有寫緩沖的情況下,每應用一條命了都需要進行一次或多次隨機磁盤寫入,這會限制系統的整體吞吐量。
Disk-based 狀態機仍然需要支持向日志落后的 Follower 提供最新的快照,而寫快照也要繼續提供服務,所以仍然需要 copy-on-write 技術以在一定期間內保持一個一致地快照傳輸。幸運的是,磁盤總是被劃分為邏輯塊,因此在狀態機中實現應該是直接的。基于磁盤的狀態機也可以依靠操作系統的支持,例如 Linux 的 LVM 也可以用來創建快照。或者是使用系統的COW支持,Linux的fork,或者是ZFS的Snapshot等。
memory-based
memory-based日志主要有Log-structured File System 或 LSM tree方式做快照
參考鏈接
- MIT 6.824 Raft 設計文檔
總結
以上是生活随笔為你收集整理的一致性协议raft详解(三):raft中的消息类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一致性协议raft详解(二):安全性
- 下一篇: 一致性协议raft详解(四):raft在