raft mit 6.824 實現領導者選舉(LAB2A)

1.程序結構

lab2的實驗是要實現以下接口

// create a new Raft server instance:
rf := Make(peers, me, persister, applyCh)

// start agreement on a new log entry:
rf.Start(command interface{}) (index, term, isleader)

// ask a Raft for its current term, and whether it thinks it is leader
rf.GetState() (term, isLeader)

// each time a new entry is committed to the log, each Raft peer
// should send an ApplyMsg to the service (or tester).
type ApplyMsg

其中參數在註釋中基本都有解釋。
Make 用來創建點對點server ,peers是所有的server,len(peers)也就是所有的server數量,me就是當前的server,也就是在peers中的下標。persister和applyCh 以及Start函數在2A實驗中沒有用上。

做實驗前強烈建議多讀幾遍raft論文。
助教的student guide 也很重要:

student guide

FAQ

locking and structure

2.常見問題

  1. 什麼時候需要重置超時選舉時間?在 studen guide中寫到有3種情況需要重置超時時間:
    a) 從當前的leader 中收到 AppendEntries ,收到過期的不需要重置
    b) 開始一輪選舉的時候,當然本輪沒有選舉結果開始下一輪的時候也應該重置
    c) 當你給其他節點投票的時候

  2. 什麼時候重置votefFor?
    當term改變的時候重置votedfor,例如投票的時候收到了term更高,那麼不管此刻是否投票過,都應該轉變爲follower並且重置votedfor;當leader掉線一段時間重連回來,此刻羣組有新leader,那麼old leader應該轉變爲follower並且重置votedfor。

  3. 在做RPC請求的時候不應該加鎖。例如:
    rf.mu.Lock()
    rf. sendRequestVote(...)
    rf.mu.UnLock()
    這種寫法是不合邏輯的,容易造成死鎖,我們僅需對共享資源加鎖就足夠了,比如這裏對參數構造加鎖。

  4. server 成爲leader之後不需要進行 選舉超時倒計時。

3.實現 給出部分代碼,僅供參考

3.1 流程

  1. 首先 raft 啓動,在Make 中做初始化,一開始所有的server都是follower,並且每個server會隨機分配一個選舉超時時間,同時所有的server都有相同的心跳間隔時間。那麼由於涉及到角色轉換,我們最好爲每一種角色寫一個轉換函數,convertToFollower,convertToCandidate,convertToLeader。
  • 什麼時候會轉變到follower呢?不外乎在心跳,投票或者appendEntries的時候收到了更加新的server的信息,這時候需要轉變到follower的同時更新自己的Term
  • 轉變到candidate只有一種,當選舉超時內沒有收到心跳或者appendEntries,那麼超時之後主動變成candidate
  • 轉變到leader,只有在candidate收到大多數請求的時候,競選成功,成爲leader
func (rf* Raft) convertToCandidate(){
    rf.raftStatus = Candidate
    rf.currentTerm++
    rf.votedFor = rf.me
    rf.nonLeaderCond.Signal() // awake electionTimeoutTick

}

func (rf* Raft) convertToFollower(newTerm int){
    rf.raftStatus = Follower
    rf.currentTerm = newTerm
    rf.votedFor = -1
    rf.nonLeaderCond.Signal() // awake electionTimeoutTick

}

func (rf* Raft) convertToLeader(){
    rf.raftStatus = Leader

}

初始化的時候,爲了防止瓜分選票的情況發生,需要隨機設置不同的超時時間,論文建議是150-300ms ,建議寫個函數,用時間戳做隨機種子,論文建議每次生成不同的超時時間。

func (rf *Raft) getRandElecTime() int {
    rand.Seed(time.Now().UnixNano())
    electionTimeout := rand.Intn(150) + 150 // [150,300)
    return electionTimeout
}
func (rf* Raft) resetElectionTimer()  {
    rf.lastHeartbeatTime = time.Now().UnixNano()
    rf.electionTimeout = rf.getRandElecTime()
}

根據論文解析,我們需要長時間運行的gorutinue (可以簡單理解爲死循環)來處理事件和統計選舉超時。

func (rf *Raft) EventLoop() () {
    for{
        rf.mu.Lock()
        if rf.killed(){
            rf.mu.Unlock()
            return
        }
        rf.mu.Unlock()
        select {
        case <- rf.electionTimeoutChan:
            rf.mu.Lock()
            DPrintf("[EventLoop]: Id %d Term %d State %s \t || \t election timeout , start an eletion\n",
                rf.me, rf.currentTerm, state2name(rf.raftStatus))
            rf.mu.Unlock()
            // start election, if election timeout
            go rf.startElection()

        case <-rf.heartbeatPeriodChan:
            DPrintf("[EventLoop]: Id %d Term %d State %s \t || \t election timeout , start to send heartbeat\n",
                rf.me, rf.currentTerm, state2name(rf.raftStatus))
            go rf.broadcastHeartbeat()
        }
    }
}

func (rf *Raft) electionTimeoutTick()  {
    for {
        rf.mu.Lock()
        _, isLeader := rf.GetState()
        if rf.killed(){
            rf.mu.Unlock()
            return
        }
        rf.mu.Unlock()
        if isLeader {
            // if is leader , no need to check election timeout
            rf.nonLeaderCond.L.Lock()
            rf.nonLeaderCond.Wait()
            rf.nonLeaderCond.L.Unlock()
        }else { // follower and candidate
            rf.mu.Lock()
            elapseTime := time.Now().UnixNano() - rf.lastHeartbeatTime
            if elapseTime/int64(time.Millisecond) > int64(rf.electionTimeout){
                DPrintf("[electionTimeoutTick]: Id %d Term %d State %s\t || \ttimeout,"+
                    "convert to candidate\n" , rf.me, rf.currentTerm ,state2name(rf.raftStatus))
                DPrintf("[electionTimeoutTick] : %d %d\n",elapseTime/int64(time.Millisecond),int64(rf.electionTimeout))
                rf.electionTimeoutChan <- true
            }
            rf.mu.Unlock()
            time.Sleep(time.Millisecond*10)
        }
    }
}

實驗部分要求心跳不超過10次/s,也就是不小於100ms/次,而心跳時間間隔應該小於選舉超時時間,因此設置爲100到150ms之間。

func (rf* Raft) broadcastHeartbeat()  {
    for {
        rf.mu.Lock()
        _,isLeader := rf.GetState()
        if rf.killed(){
            rf.mu.Unlock()
            return
        }
        rf.mu.Unlock()

        if !isLeader {
            // not leader , then return
            return
        }

        // send heart beat
        for i,_ := range rf.peers{
            if i == rf.me{
                continue
            }
            rf.mu.Lock()
            //prevLogIndex := len(rf.log)-1
            prevLogIndex,PrevLogTerm :=rf.getLastLogInfo()
            args := AppendEntriesArgs{Term: rf.currentTerm,LeaderId: rf.me,PrevLogIndex: prevLogIndex,
                PrevLogTerm: PrevLogTerm, LeaderCommit: rf.commitIndex}
            var reply AppendEntriesReply
            rf.mu.Unlock()
            go func(index int, args *AppendEntriesArgs, reply* AppendEntriesReply) {
                ok := rf.sendAppendEntries(index,args,reply)
                rf.mu.Lock()
                defer rf.mu.Unlock()
                if ok == false{
                    // send heartbeat failed
                    //DPrintf("[broadcastHeartbeat]: %d send to peer's id %d failed term %d\n",args.LeaderId,index,reply.Term)

                }else {
                    //DPrintf("[broadcastHeartbeat]: Id %d Term %d State %s\t || \ttimeout,"+
                    //  "send heartbeat to %d success\n" , rf.me, rf.currentTerm ,state2name(rf.raftStatus),index)

                    if reply.Term > rf.currentTerm{
                        //
                        DPrintf("[broadcastHeartbeat]: Id %d Term %d State %s\t || \ttimeout,"+
                            "send heartbeat failed, reply term %d\n" , rf.me, rf.currentTerm ,state2name(rf.raftStatus),reply.Term)
                        rf.convertToFollower(reply.Term)

                    }else if reply.Term == rf.currentTerm && reply.Success == false{
                        // follower's log index and log term not match

                    }else {
                        //
                    }

                }
                // check reply


            }(i,&args,&reply)
        }

        // sleep
        time.Sleep(time.Duration(rf.heartbeatInterval)*time.Millisecond)
    }

}
  1. 開始選舉。當某一臺server 的選舉超時計時先倒數完,這臺server按照規則,先給變成candidate,term加一,給自己投票,重置選舉超時計時器,然後讓其他server投票。只要超過半數,那麼就當選leader,開始給其餘節點發心跳。
func (rf* Raft) startElection()  {
    rf.mu.Lock()
    rf.convertToCandidate()
    nVotes := 1 // has voted in the term
    // 3.reset election timeout
    rf.resetElectionTimer()
    DPrintf("[startElection]: Id %d Term %d state %s \n",rf.me, rf.currentTerm, state2name(rf.raftStatus))
    rf.mu.Unlock()
    // 4.send request vote to other server
    go func(nVotes* int ,rf* Raft) {
        var wg sync.WaitGroup
        winThreadHold := len(rf.peers)/2 + 1

        for i,_ := range rf.peers{
            if i == rf.me{
                continue
            }
            rf.mu.Lock()
            lastLogIndex ,LastLogTerm := rf.getLastLogInfo()

            reqArgs := RequestVoteArgs{Term: rf.currentTerm,CandidateId: rf.me, LastLogIndex: lastLogIndex,LastLogTerm: LastLogTerm}
            rf.mu.Unlock()

            wg.Add(1)

            var reply RequestVoteReply
            go func(index int, rf* Raft, args* RequestVoteArgs, reply* RequestVoteReply) {
                defer wg.Done()
                DPrintf("[startElection]: Id %d Term %d state %s \t || \t" +
                    "start send request vote to %d \n",rf.me, rf.currentTerm, state2name(rf.raftStatus),index)

                ok := rf.sendRequestVote(index,args,reply)
                if ok == false {
                    DPrintf("[startElection]: Id %d Term %d state %s \t || \t" +
                        "send request vote to %d failed \n",rf.me, rf.currentTerm, state2name(rf.raftStatus),index)
                    return
                }

                rf.mu.Lock()
                defer rf.mu.Unlock()
                // reject vote
                if reply.VoteGranted == false{
                    if reply.Term > rf.currentTerm{
                        DPrintf("[startElection]: Id %d Term %d state %s \t || \t" +
                            "peer term:%d \n",rf.me, rf.currentTerm, state2name(rf.raftStatus),reply.Term)
                        rf.convertToFollower(reply.Term)
                    }
                }else{
                    *nVotes += 1
                    // if it self has became leader , then no need to do
                    _,isLeader := rf.GetState()
                    if isLeader{
                        return
                    }
                    if rf.raftStatus == Candidate && *nVotes >= winThreadHold {
                        DPrintf("[startElection]: Id %d Term %d state %s \t || \t" +
                            "win election votes:%d \n",rf.me, rf.currentTerm, state2name(rf.raftStatus),*nVotes)

                        // win election
                        rf.convertToLeader()

                        // re init nextIndex
                        rf.reInitNextIndex()

                        // send heartbeat immediately to all server
                        DPrintf("start send heartbeat\n")
                        go rf.broadcastHeartbeat()
                    }
                }

            }(i,rf,&reqArgs,&reply)
        }

        // wait all send finish
        wg.Wait()

    }(&nVotes,rf)

}
  1. 處理投票
    那麼這裏主要是根據term和lastlogindex來判斷是否投票。
    不投票?
    a) 比自己Term小
    b) log比自己舊
    c) 在本次Term中已經投票過了(args.Term == rf.currentTerm && rf.votedFor!= -1)

  2. 處理心跳
    論文說了,entries爲空表示心跳信息。
    如果收到了term更高的發來的心跳,應該轉變爲follower並重置選舉超時計時器
    收到當前term的,直接重置選舉超時計時器
    同時我們應該在reply中附上自己的term,以便leader檢測自己是否是過時的leader(leader斷線重連,term比自己小)

本篇文章由一文多發平臺ArtiPub自動發佈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章