日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一致性协议raft详解(三):raft中的消息类型

發布時間:2024/2/28 编程问答 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一致性协议raft详解(三):raft中的消息类型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一致性協議raft詳解(三):raft中的消息類型

    • 前言
    • raft 節點
    • Raft中RPC的種類
      • RequestVote
        • leader選舉成功后
      • AppendEntries
        • 請求參數
        • 返回值
          • 存儲日志(日志同步過程)
      • InstallSnapshot RPC
        • 快照的并發性
        • 快照實現以及何時做快照
        • 快照實現
          • disk-based
          • memory-based
    • 參考鏈接

前言

有關一致性協議的資料網上有很多,當然錯誤也有很多。筆者在學習的過程中走了不少彎路。現在回過頭來看,最好的學習資料就是Leslie LamportDiego Ongaro的數篇論文、Ongaro在youtube上發的三個視頻講解,以及何登成的ppt。

本系列文章是只是筆者在學習一致性協議過程中的摘抄和總結,有疏漏之處敬請諒解,歡迎討論。

raft 節點

Raft算法中服務器有三種角色

  • Follower
  • Candidate
  • Leader
  • 每個服務器上都會存儲的持久狀態:

  • currentTerm: 當前節點所能看到的最大的term值, 初始化為0并單調遞增
  • votedFor: 當前term里將票投給對象的candidateId, 如果尚未投票則為空(我實現時置為-1)
  • log[]: 日志條目(每條日志條目包含命令和任期), 會按順序作用于狀態機, 第一個索引Index為1
  • 每個服務器上都會存儲的易失狀態:

  • commitIndex: 當前服務器已知已提交的最高的日志條目的索引(每次選舉之后leader將其初始為0,單調遞增)(這個代表了整個raft集群的最后一個index,根據figure8,這個參數有可能因為其他節點而被改變)
  • 所謂的CommitIndex,就是已經達成多數派,可以應用的最新日志位置
  • lastApplied: 當前服務器已經被應用到狀態機的最高的日志條目的索引(初始值為0,單調遞增)(這個參數代表了自己這個節點目前到底持久化了多少日志)
  • 上面兩個index只是索引,可能會有空擋,比如某個log entry沒有commit上

    在狀態為Leader的服務器上會額外存儲的易失狀態:

  • nextIndex[]: 針對每個其他節點, 下一個需要發送的日志條目的索引, 初始化為leader最后一個日志索引+1
  • matchIndex[]: 針對每個其他節點, 當前所知的和Leader匹配的最高日志索引, 初始化為0并單調遞增
  • Raft中RPC的種類

    RequestVote

    candidate節點請求其他節點投票給自己

    請求參數:

  • term: 當前candidate節點的term值
  • candidateId: 當前candidate節點的編號
  • lastLogIndex: 當前candidate節點最后一個日志的索引
  • lastLogTerm: 當前candidate節點最后一個日志的term值
  • 返回值:

  • term: 接受投票節點的term值, 主要用來更新當前candidate節點的term值
  • voteGranted: 是否給該申請節點投票
  • 一個節點(無論當前是什么狀態)在接收到RequestVote(term, candidateId, lastLogIndex, lastLogTerm)消息時, 其會做如下判斷:

  • 如果參數攜帶的term < currentTerm, 則返回currentTerm并拒絕投票請求: (currentTerm, false), 并保持當前節點狀態不變
  • 如果當前term voteFor=null,做以下檢查:
  • 如果參數攜帶的term > currentTerm
  • leader會stepdown,并且提升term,然后重新選主(這點可以通過Leader Stickiness進行優化)
  • follower會拒絕leader的請求,提升term,然后重新選主
  • 經過以上的過程之后,節點仍需要將request lastLogIndex和自己的最后一條日志的index進行比較(leader就是最后一條日志(比如lastapplied或者最后一個log的index),follower就是commitIndex),確保candidate節點的日志至少和自己一樣新,才可以同意RequestVote RPC
  • 如果參數攜帶的term = currentTerm,直接判斷candidate的日志是否至少和自己一樣新,如果是則同意RequestVote RPC
  • 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的統治地位

    請求參數

  • term: 當前leader節點的term值
  • leaderId: 當前leader節點的編號(注:follower根據領導者id把客戶端的請求重定向到領導者,比如有時客戶端把請求發給了follower而不是leader)
  • prevLogIndex: 當前發送的日志的前面一個日志的索引
  • prevLogTerm: 當前發送的日志的前面一個日志的term值 (這個和上一個作用是follower日志有效性檢查)
  • entries[]: 需要各個節點存儲的日志條目(用作心跳包時為空, 可能會出于效率發送超過一個日志條目)
  • leaderCommit: 當前leader節點最高的被提交的日志的索引(就是leader節點的commitIndex)
  • 返回值

  • term: 接收日志節點的term值, 主要用來更新當前leader節點的term值
  • success: 如果接收日志節點的log[]結構中prevLogIndex索引處含有日志并且該日志的term等于prevLogTerm則返回true, 否則返回false
  • 一個節點(無論當前是什么狀態)接收到AppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries[], leaderCommit)消息時, 其會做如下判斷(條件從上往下依次判斷):

  • 如果參數攜帶的term < currentTerm, 則返回當前term并返回: (currentTerm, false), 并保持當前節點狀態不變
  • 如果參數攜帶的term >= currentTerm, 則設置currentTerm = term, voteFor = leaderId, 轉換當前節點為Follower狀態, 重置隨機定時器, 進入下一步判斷:
  • 如果當前節點log[]結構中prevLogIndex索引處不含有日志, 則返回(currentTerm, false)
  • 如果當前節點log[]結構中prevLogIndex索引處含有日志但該日志的term不等于prevLogTerm, 則返回(currentTerm, false)
  • 如果當前節點log[]結構中prevLogIndex索引處含有日志并且該日志的term等于prevLogTerm, 則執行存儲日志, 然后應用日志到狀態機并返回(currentTerm, true)
  • 以上三點說明了,log在一個節點上是順序append的 (日志提交的順序:先append再apply)
  • 存儲日志(日志同步過程)
  • Leader上為每個節點維護NextIndex、MatchIndex,NextIndex表示待發往該節點的Entry index,MatchIndex表示該節點已匹配的Entry index,同時每個節點維護CommitIndex表示當前已提交的Entry index。轉為Leader后會將所有節點的NextIndex置為自己最后一條日志index+1,MatchIndex全置0,同時將自身CommitIndex置0
  • Leader節點不斷將user_data轉為Entry追加到日志文件末尾,Entry包含index、term和user_data,其中index在日志文件中從1開始順序分配,term為Leader當前的term。
  • Leader通過AppendEntry RPC將Entry同步到Followers,Follower收到后校驗該Entry之前的日志是否已匹配。如匹配則直接寫入Entry,返回成功;否則刪除不匹配的日志,返回失敗。校驗是通過在AppendEntry RPC中攜帶待寫入Entry的前一條entry信息完成。
  • 當Follower返回成功時,leader更新對應節點的NextIndex和MatchIndex,繼續發送后續的Entry。如果MatchIndex更新后,大多數節點的MatchIndex已大于CommitIndex,則更新CommitIndex。Follower返回失敗時回退NextIndex繼續發送,直到Follower返回成功。
  • Leader每次AppendEntry RPC中會攜帶當前最新的LeaderCommitIndex,Follower寫入成功時會將自身CommitIndex更新為Min(LastLogIndex,LeaderCommitIndex)。
  • 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 可能已經被丟棄了。

    和配置變化不同,不同的系統有不同的日志壓縮方式,取決于你的性能考量,以及基于硬盤還是基于內存。日志壓縮的大部分責任都落在狀態機上。

    不同的壓縮方法有幾個核心的共同點:

  • 不將壓縮決定集中在 Leader 上,每個服務器獨立地壓縮其已提交的日志。這就避免了 Leader 將日志傳遞給已有該日志的 Follower,同時也增強了模塊化,減少交互,將整個系統的復雜性最小化。(對于非常小的狀態機,基于 Leader 的日志壓縮也許更好。)
  • 將之前的 log 的維護責任從 Raft 轉移到狀態機。Raft 要保存最后被丟棄的記錄的index和term,用于 AppendEntries RPC一致性檢查。同時,也需要保存最新的配置信息:成員變更失敗需要回退配置,最近的配置必須保存。
  • 一旦丟棄了前面部分的日志,狀態機就承擔兩個新的責任:
  • 如果服務器重啟了,需要將最新的快照加載到狀態機后再接受 log;此外,
  • 需要向較慢的 follower(日志遠落后于 Leader)發送一致的狀態鏡像。(InstallSnapshot RPC)
  • 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中的消息类型的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。