好的實(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代碼走讀
上面是raft中一個node做的事,Node代表raft集群中的一個節(jié)點(diǎn),剛開始node是follower,然后隨著
tickc的進(jìn)行,開始進(jìn)入選舉,raft在變?yōu)閒ollower的時候做了下面幾件事:初始化了tick函數(shù)
tickElection,用來開始選舉,做判斷后,調(diào)用Step判斷消息類型為
MsgHup,于是進(jìn)入campaign選舉函數(shù)中做的事情
轉(zhuǎn)換成candidate時,開始一個選舉:
- 遞增currentTerm;投票給自己;
- 重置election timer;
- 向所有的服務(wù)器發(fā)送 RequestVote RPC請求
接著看下send函數(shù)
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,
自后調(diào)用了node的send函數(shù),此時是拒絕的,因為已經(jīng)是candidate狀態(tài)了,而如果是follower,其處理函數(shù)是
stepFollower,其規(guī)則就是之前說到的:
如果本地的voteFor為空或者為candidateId,并且候選者的日志至少與接受者的日志一樣新,則投給其選票
進(jìn)行到這,我們看到了follower在收到vote rpc后的處理,下面是candidate的處理了。
回到之前調(diào)用Ready(),接著應(yīng)該調(diào)用Advance,
里面會設(shè)置
advancec,好了,運(yùn)行到這,我們又要回到node.run中了
此時的狀態(tài)是:candidate,advancec中有數(shù)據(jù),接著來看candidate在發(fā)送出vote rpc,接收到響應(yīng)的處理,網(wǎng)絡(luò)層的Send函數(shù)是:
Send會調(diào)用
Peer.send,函數(shù)注釋說:此函數(shù)是非阻塞的,不保證請求一定能被peer收到一般常理我們發(fā)送后,等待響應(yīng)后再處理,但是找了很久也沒找到常理函數(shù),這個時候,我們再去看下follower對于投票的處理
發(fā)現(xiàn)在響應(yīng)上也是通過發(fā)送一個消息來響應(yīng)的,因此我們此時可以看到peer之間的交互不是傳統(tǒng)意義上的request-response模型了。
我們?nèi)タ磳τ?code>MsgVoteResp的處理,其入口都是通過調(diào)用node.Step函數(shù),此時如果得到大多數(shù)票選,則成為leader
看becomeLeader函數(shù)
在leader函數(shù)中,最重要的就是發(fā)送命令了,我們看看這個過程
這是通過node.Propose函數(shù)實(shí)現(xiàn)的
到最后又是通過step函數(shù)
里面挨個調(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中,會去嘗試更新committed index,然后接著看AppendResp的處理去檢查各個peer的matchIndex,然后嘗試更新commitIndex
下一個問題,接著去看commitIndex > lastAppied后,在哪兒去應(yīng)用log到狀態(tài)機(jī)的
這是通過node.run中readyc和advancec來實(shí)現(xiàn)的
上面就是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)針對每個請求的處理都是非常明確的。