raft 系列解讀(4) 之 etcd-raft學(xué)習(xí)

好的實(shí)現(xiàn),看看別人怎么寫的,github

大多數(shù)Raft的實(shí)現(xiàn)都是整體設(shè)計,包括存儲處理,消息序列化和網(wǎng)絡(luò)傳輸,但是本raft庫在實(shí)現(xiàn)的時候只實(shí)現(xiàn)了最核心的算法,換來了靈活性和性能,網(wǎng)絡(luò)和disk IO部分都由使用者實(shí)現(xiàn),使用者需要實(shí)現(xiàn)自己的消息傳送層,同時,需要自己實(shí)現(xiàn)持久化部分來存儲Raft log和state。
為了實(shí)現(xiàn)Raft庫的可測性,庫在實(shí)現(xiàn)的時候?qū)aft建模為一個狀態(tài)機(jī),輸入是消息,可能是本地時間的更新或者網(wǎng)絡(luò)消息,狀態(tài)機(jī)的輸出是一個3元組:{[]Messages, []LogEntries, NextState}。

第一步是使用,怎么使用raft來搭建自己的key-value系統(tǒng)

etcd-raft代碼走讀

node-run

上面是raft中一個node做的事,Node代表raft集群中的一個節(jié)點(diǎn),剛開始node是follower,然后隨著tickc的進(jìn)行,開始進(jìn)入選舉,raft在變?yōu)閒ollower的時候做了下面幾件事:
becomeFollower

初始化了tick函數(shù)tickElection,用來開始選舉,做判斷后,調(diào)用Step
選舉函數(shù)

判斷消息類型為MsgHup,于是進(jìn)入campaign
進(jìn)行選舉

選舉函數(shù)中做的事情
選舉

轉(zhuǎn)換成candidate時,開始一個選舉:

  1. 遞增currentTerm;投票給自己;
  2. 重置election timer;
  3. 向所有的服務(wù)器發(fā)送 RequestVote RPC請求

成為candidate

接著看下send函數(shù)
raft.send

send函數(shù)中將消息存儲了msgs中,在哪兒消費(fèi)呢?通過讀取newReady來返回Ready
圖片

此時又返回到node.run中,此時因為會進(jìn)入case readyc <- rd分支
圖片

在里面做的事情
圖片

msgs因為已經(jīng)讀取過了,設(shè)置為空,并且會賦值advancec,進(jìn)行到這readyc中已經(jīng)有一個數(shù)據(jù), 而此channel會在函數(shù)Ready中返回給外面,下面接著看誰會去讀Ready

func (n *node) Ready() <-chan Ready { return n.readyc }

讀取Ready的是應(yīng)用程序,看下Ready()函數(shù)的說明

//=> 讀取到當(dāng)前狀態(tài),當(dāng)從Ready()取出狀態(tài)后,需要調(diào)用 Advance
//=> 注意:只有當(dāng)所有提交的entries都應(yīng)用后,才會調(diào)用下一個 Ready 的狀態(tài)

我們回到之前的選舉上,讀取到的Ready里面包含了Vote消息,我們會調(diào)用網(wǎng)絡(luò)層發(fā)送消息出去,并且調(diào)用Advance(),而此時其他Node接收到網(wǎng)絡(luò)層消息后,會調(diào)用Step()函數(shù),在成為candidate的時候,我們設(shè)置了step函數(shù)為stepCandidate,

stepCandidate

自后調(diào)用了node的send函數(shù),此時是拒絕的,因為已經(jīng)是candidate狀態(tài)了,而如果是follower,其處理函數(shù)是stepFollower,
stepFollower

其規(guī)則就是之前說到的:

如果本地的voteFor為空或者為candidateId,并且候選者的日志至少與接受者的日志一樣新,則投給其選票

進(jìn)行到這,我們看到了follower在收到vote rpc后的處理,下面是candidate的處理了。

回到之前調(diào)用Ready(),接著應(yīng)該調(diào)用Advance,

Advance

里面會設(shè)置advancec,好了,運(yùn)行到這,我們又要回到node.run中了

此時的狀態(tài)是:candidate,advancec中有數(shù)據(jù),接著來看candidate在發(fā)送出vote rpc,接收到響應(yīng)的處理,網(wǎng)絡(luò)層的Send函數(shù)是:

Transport.Send

Send會調(diào)用Peer.send,函數(shù)注釋說:此函數(shù)是非阻塞的,不保證請求一定能被peer收到
Peer.send

一般常理我們發(fā)送后,等待響應(yīng)后再處理,但是找了很久也沒找到常理函數(shù),這個時候,我們再去看下follower對于投票的處理
send.MsgVoteResp

發(fā)現(xiàn)在響應(yīng)上也是通過發(fā)送一個消息來響應(yīng)的,因此我們此時可以看到peer之間的交互不是傳統(tǒng)意義上的request-response模型了。

我們?nèi)タ磳τ?code>MsgVoteResp的處理,其入口都是通過調(diào)用node.Step函數(shù),此時如果得到大多數(shù)票選,則成為leader

MsgVoteResp處理

看becomeLeader函數(shù)
becomeLeader

在leader函數(shù)中,最重要的就是發(fā)送命令了,我們看看這個過程

這是通過node.Propose函數(shù)實(shí)現(xiàn)的

node.Propose

到最后又是通過step函數(shù)
stepLeader

里面挨個調(diào)用send函數(shù)

func (r *raft) bcastAppend() {
    for id := range r.prs {
        if id == r.id {
            continue
        }
        r.sendAppend(id)
    }
}

sendAppend

看完發(fā)送端,接著看follower的接收端處理
handleAppendEntries

細(xì)看handleAppendEntries函數(shù),就是去做raft協(xié)議中規(guī)定的操作了
圖片

maybeAppend中,會去嘗試更新committed index,然后接著看AppendResp的處理
AppendResp

去檢查各個peer的matchIndex,然后嘗試更新commitIndex

下一個問題,接著去看commitIndex > lastAppied后,在哪兒去應(yīng)用log到狀態(tài)機(jī)的
這是通過node.runreadycadvancec來實(shí)現(xiàn)的

node.run

上面就是etcd中raft的大致流程,有一個機(jī)遇raft實(shí)現(xiàn)的簡單key-value系統(tǒng),github地址:https://github.com/zhuanxuhit/distributed-system/tree/master/etcd-raft

讀完代碼后,最大的一個感受是整個node在實(shí)現(xiàn)的時候都是無鎖的,其技巧是通過go的channel將所有請求串行化,然后另一個特點(diǎn)是根據(jù)不同的狀態(tài),設(shè)置不同的處理函數(shù),整個實(shí)現(xiàn)非常的清晰,因為每個狀態(tài)針對每個請求的處理都是非常明確的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容