好的實(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代碼走讀
上面是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í)候做了下面幾件事:初始化了tick函數(shù)
tickElection
,用來(lái)開(kāi)始選舉,做判斷后,調(diào)用Step
判斷消息類型為
MsgHup
,于是進(jìn)入campaign
選舉函數(shù)中做的事情
轉(zhuǎn)換成candidate時(shí),開(kāi)始一個(gè)選舉:
- 遞增currentTerm;投票給自己;
- 重置election timer;
- 向所有的服務(wù)器發(fā)送 RequestVote RPC請(qǐng)求
接著看下send函數(shù)
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
,
自后調(diào)用了node的send函數(shù),此時(shí)是拒絕的,因?yàn)橐呀?jīng)是candidate狀態(tài)了,而如果是follower,其處理函數(shù)是
stepFollower
,其規(guī)則就是之前說(shuō)到的:
如果本地的voteFor為空或者為candidateId,并且候選者的日志至少與接受者的日志一樣新,則投給其選票
進(jìn)行到這,我們看到了follower在收到vote rpc后的處理,下面是candidate的處理了。
回到之前調(diào)用Ready()
,接著應(yīng)該調(diào)用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ù)是:
Send會(huì)調(diào)用
Peer.send
,函數(shù)注釋說(shuō):此函數(shù)是非阻塞的,不保證請(qǐng)求一定能被peer收到一般常理我們發(fā)送后,等待響應(yīng)后再處理,但是找了很久也沒(méi)找到常理函數(shù),這個(gè)時(shí)候,我們?cè)偃タ聪耭ollower對(duì)于投票的處理
發(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
看becomeLeader函數(shù)
在leader函數(shù)中,最重要的就是發(fā)送命令了,我們看看這個(gè)過(guò)程
這是通過(guò)node.Propose
函數(shù)實(shí)現(xiàn)的
到最后又是通過(guò)step函數(shù)
里面挨個(gè)調(diào)用send函數(shù)
func (r *raft) bcastAppend() {
for id := range r.prs {
if id == r.id {
continue
}
r.sendAppend(id)
}
}
看完發(fā)送端,接著看follower的接收端處理
細(xì)看handleAppendEntries函數(shù),就是去做raft協(xié)議中規(guī)定的操作了
在
maybeAppend
中,會(huì)去嘗試更新committed index
,然后接著看AppendResp的處理去檢查各個(gè)peer的matchIndex,然后嘗試更新commitIndex
下一個(gè)問(wèn)題,接著去看commitIndex > lastAppied后,在哪兒去應(yīng)用log到狀態(tài)機(jī)的
這是通過(guò)node.run
中readyc
和advancec
來(lái)實(shí)現(xiàn)的
上面就是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)求的處理都是非常明確的。