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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

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

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

前言

有關(guān)一致性協(xié)議的資料網(wǎng)上有很多,當(dāng)然錯誤也有很多。筆者在學(xué)習(xí)的過程中走了不少彎路。現(xiàn)在回過頭來看,最好的學(xué)習(xí)資料就是Leslie LamportDiego Ongaro的數(shù)篇論文、Ongaro在youtube上發(fā)的三個視頻講解,以及何登成的ppt。

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

raft 節(jié)點

Raft算法中服務(wù)器有三種角色

  • Follower
  • Candidate
  • Leader
  • 每個服務(wù)器上都會存儲的持久狀態(tài):

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

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

    在狀態(tài)為Leader的服務(wù)器上會額外存儲的易失狀態(tài):

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

    RequestVote

    candidate節(jié)點請求其他節(jié)點投票給自己

    請求參數(shù):

  • term: 當(dāng)前candidate節(jié)點的term值
  • candidateId: 當(dāng)前candidate節(jié)點的編號
  • lastLogIndex: 當(dāng)前candidate節(jié)點最后一個日志的索引
  • lastLogTerm: 當(dāng)前candidate節(jié)點最后一個日志的term值
  • 返回值:

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

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

    領(lǐng)導(dǎo)人:

    • 一旦成為領(lǐng)導(dǎo)人:發(fā)送空的附加日志 RPC(心跳)給其他所有的服務(wù)器;在一定的空余時間之后不停的重復(fù)發(fā)送,以阻止follower超時(5.2 節(jié))
    • 如果接收到來自客戶端的請求:附加條目到本地日志中,在條目被應(yīng)用到狀態(tài)機后響應(yīng)客戶端(5.3 節(jié))
    • 如果對于一個follower,如果leader發(fā)現(xiàn)自己的最后日志條目的索引值大于等于 nextIndex,那么:發(fā)送從 nextIndex 開始的所有日志條目:
      • 如果成功:更新相應(yīng)follower的 nextIndex 和 matchIndex
      • 如果因為日志不一致而失敗,減少 nextIndex 重試
    • 如果存在一個滿足N > commitIndex的 N,并且大多數(shù)的matchIndex[i] ≥ N成立,并且log[N].term == currentTerm成立,那么令 commitIndex 等于這個 N (5.3 和 5.4 節(jié)) (figure 8),這樣的話,leader就可以把漏下的日志補上
      • 之所以這么做,是因為在新的leader選舉的過程中,老的leader是可以繼續(xù)生效的,那么也就導(dǎo)致新的leader可能確實了一部分老leader最后commit的日志,或者network partition了,某個節(jié)點的term很大,導(dǎo)致其一定是主,但是這個主上有很多漏掉的leader

    AppendEntries

    leader節(jié)點使用該消息向其他節(jié)點同步日志, 或者發(fā)送空消息作為心跳包以維持leader的統(tǒng)治地位

    請求參數(shù)

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

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

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

    InstallSnapshot RPC

    該rpc主要用于leader將集群的快照同步給其他節(jié)點。這里主要講一下快照的機制:

    本節(jié)主要參考文章條分縷析 Raft 算法(續(xù)):日志壓縮和性能優(yōu)化

    log過多就需要做快照,最初設(shè)計 LogCabin 的時候沒有考慮日志壓縮,因此代碼上假定了如果 entry i 在日志中,那么 entry 1 到 i - 1 也一定在日志中。有了日志壓縮,這就不再成立了,前面的 entry 可能已經(jīng)被丟棄了。

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

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

  • 不將壓縮決定集中在 Leader 上,每個服務(wù)器獨立地壓縮其已提交的日志。這就避免了 Leader 將日志傳遞給已有該日志的 Follower,同時也增強了模塊化,減少交互,將整個系統(tǒng)的復(fù)雜性最小化。(對于非常小的狀態(tài)機,基于 Leader 的日志壓縮也許更好。)
  • 將之前的 log 的維護責(zé)任從 Raft 轉(zhuǎn)移到狀態(tài)機。Raft 要保存最后被丟棄的記錄的index和term,用于 AppendEntries RPC一致性檢查。同時,也需要保存最新的配置信息:成員變更失敗需要回退配置,最近的配置必須保存。
  • 一旦丟棄了前面部分的日志,狀態(tài)機就承擔(dān)兩個新的責(zé)任:
  • 如果服務(wù)器重啟了,需要將最新的快照加載到狀態(tài)機后再接受 log;此外,
  • 需要向較慢的 follower(日志遠落后于 Leader)發(fā)送一致的狀態(tài)鏡像。(InstallSnapshot RPC)
  • memory-based 狀態(tài)機的快照的大部分工作是序列化內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)。

    快照的并發(fā)性

    創(chuàng)建一個快照需要耗費很長時間,包括序列化和寫入磁盤。**因此,序列化和寫快照都要與常規(guī)操作并發(fā)進行,避免服務(wù)不可用。**copy-on-write 技術(shù)允許進行新的更新而不影響寫快照。有兩個方法來實現(xiàn):

    • 狀態(tài)機可以用不可變的(immutable)數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)。因為狀態(tài)機命令不會 in-place 的方式來修改狀態(tài)(通常使用追加的方式),快照任務(wù)可以引用之前狀態(tài)的并把狀態(tài)一致地寫入到快照。
    • 另外,也可以使用操作系統(tǒng)的 copy-on-write。例如,在 Linux 上可以使用 fork 來復(fù)制父進程的整個地址空間,然后子進程就可以把狀態(tài)機的狀態(tài)寫出并退出,整個過程中父進程都可以持續(xù)地提供服務(wù)。LogCabin中當(dāng)前使用的就是這種方法。

    快照實現(xiàn)以及何時做快照

    服務(wù)器需要決定什么時候做快照。太過頻繁地做快照,將會浪費磁盤帶寬和其他資源太不頻繁地做快照,則有存儲空間耗盡的風(fēng)險,并且重啟服務(wù)需要更長的重放日志時間。

    **一個簡單的策略是設(shè)置一個閾值,當(dāng)日志大小超過閾值則做快照。**然而,這會導(dǎo)致對于小型狀態(tài)機時有著不必要的大日志。

    一個更好的方法是引入快照大小和日志大小的對比,如果日志超過快照好幾倍,可能就需要做快照。但是在做快照之前計算快照的大小是困難并且繁重的,會引入額外負擔(dān)。所以使用前一個快照的大小是比較合理的行為,一旦日志大小超過之前的快照的大小乘以擴展因子(expansion factor),服務(wù)器就做快照。

    這個擴展因子權(quán)衡空間和帶寬利用率。例如,擴展因子為 4 的話會有 20% 的帶寬用于快照(每1byte 的快照寫入有對應(yīng)的 4bytes 的 log 寫入)和大約 6 倍的硬盤空間使用(舊的快照+日志+新的快照)。

    快照仍然會導(dǎo)致 CPU 和磁盤的占用率突發(fā),可以增加額外的磁盤來減輕該現(xiàn)象。

    **同時,可以通過調(diào)度使得做快照對客戶端請求沒有影響。**服務(wù)器需要協(xié)調(diào)保證在某一時刻集群只有小部分成員集同時在做快照。由于 Raft 是多數(shù)派成員構(gòu)成的 commit,所以這樣就不會影響請求的提交了。當(dāng) Leader 想做快照的時候,首先要先下臺,讓其他服務(wù)器選出另一個 Leader 接替工作。如果這個方法充分地可行,就可能消除快照的并發(fā),服務(wù)器在快照期間其實是不可用的(這可能會造成集群的容錯能力降低的問題)。這是一個令人興奮的提升集群性能并降低實現(xiàn)機制的機會。(這里其實可以通過實現(xiàn)指定服務(wù)器做快照來優(yōu)化,braft 里就有提到這點。

    快照實現(xiàn)

    根據(jù)log的實現(xiàn)方式不同(分為memory-based和disk-based),快照也有不同的實現(xiàn)方式

    disk-based

    對于幾十或上百 GB 的狀態(tài)機,需要使用磁盤作為主要存儲。對于每一條記錄,當(dāng)其被提交并應(yīng)用到狀態(tài)機后,其實就可以被丟棄了,因為磁盤已經(jīng)持久化存儲了,可以理解為每條日志就做了一個快照。

    Disk-based 狀態(tài)機的主要問題是,磁盤會導(dǎo)致性能不佳。在沒有寫緩沖的情況下,每應(yīng)用一條命了都需要進行一次或多次隨機磁盤寫入,這會限制系統(tǒng)的整體吞吐量。

    Disk-based 狀態(tài)機仍然需要支持向日志落后的 Follower 提供最新的快照,而寫快照也要繼續(xù)提供服務(wù),所以仍然需要 copy-on-write 技術(shù)以在一定期間內(nèi)保持一個一致地快照傳輸。幸運的是,磁盤總是被劃分為邏輯塊,因此在狀態(tài)機中實現(xiàn)應(yīng)該是直接的?;诖疟P的狀態(tài)機也可以依靠操作系統(tǒng)的支持,例如 Linux 的 LVM 也可以用來創(chuàng)建快照?;蛘呤鞘褂孟到y(tǒng)的COW支持,Linux的fork,或者是ZFS的Snapshot等。

    memory-based

    memory-based日志主要有Log-structured File System 或 LSM tree方式做快照

    參考鏈接

    • MIT 6.824 Raft 設(shè)計文檔

    總結(jié)

    以上是生活随笔為你收集整理的一致性协议raft详解(三):raft中的消息类型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。