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

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

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

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

etcd-raft代碼走讀

node-run

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

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

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

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

轉(zhuǎn)換成candidate時(shí),開(kāi)始一個(gè)選舉:

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

成為candidate

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

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

此時(shí)又返回到node.run中,此時(shí)因?yàn)闀?huì)進(jìn)入case readyc <- rd分支
圖片

在里面做的事情
圖片

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

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

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

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

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

stepCandidate

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

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

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

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

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

Advance

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

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

Transport.Send

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

一般常理我們發(fā)送后,等待響應(yīng)后再處理,但是找了很久也沒(méi)找到常理函數(shù),這個(gè)時(shí)候,我們?cè)偃タ聪耭ollower對(duì)于投票的處理
send.MsgVoteResp

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

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

MsgVoteResp處理

看becomeLeader函數(shù)
becomeLeader

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

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

node.Propose

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

里面挨個(gè)調(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中,會(huì)去嘗試更新committed index,然后接著看AppendResp的處理
AppendResp

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

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

node.run

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容