用 Go 语言实现 Raft 选主

用 Go 语言实现 Raft 选主

选主模块主要包括三大功能:

  • candidate状态下的选主功能
  • leader状态下的心跳广播功能
  • follower状态下的确认功能

candidate状态下的选主功能

candidate状态下的选主功能需要关注两个方面:

  • 何时进入candidate状态,进行选主?
  • 选主的逻辑是怎样的?

首先,来讨论何时进入candidate状态,进行选主

在一定时间内没有收到来自leader或者其他candidate的有效RPC时,将会触发选主。这里需要关注的是有效两个字,要么是leader发的有效的心跳信息,要么是candidate发的是有效的选主信息,即server本身确认这些信息是有效的后,才会重新更新超时时间,超时时间根据raft论文中推荐设置为[150ms,300ms],并且每次是随机生成的值。

其次,来讨论选主的逻辑。

server首先会进行选主的初始化操作,即server会增加其term,把状态改成candidate,然后选举自己为主,并把选主的RPC并行地发送给集群中其他的server,根据返回的RPC的情况的不同,做不同的处理:

  • 该server被选为leader
  • 其他的server选为leader
  • 一段时间后,没有server被选为leader

针对情况一,该server被选为leader,当前仅当在大多数的server投票给该server时。当其被选为主时,会立马发送心跳消息给其他的server,来表明其已经是leader,防止发生新的选举。

针对情况二,其他的server被选为leader,它会收到leader发送的心跳信息,此时,该server应该转为follower,然后退出选举。

针对情况三,一段时间后,没有server被选为leader,这种情况发生在没有server获得了大多数的server的投票情况下,此时,应该发起新一轮的选举。

leader状态下的心跳广播功能

当某个server被选为leader后,需要广播心跳信息,表明其是leader,主要在以下两个场景触发:

  • server刚当选为leader
  • server周期性的发送心跳消息,防止其他的server进入candidate选举状态

leader广播心跳的逻辑为,如果广播的心跳信息得到了大多数的server的确认,那么更新leader自身的选举超时时间,防止发生重新选举。

follower状态下的确认功能

主要包括对candidate发的选举RPC以及leader发来的心跳RPC的确认功能。

对于选举RPC,假设candidate c发送选举RPC到该follower,由于follower每个term只能选举一个server,因此,只有当一个follower没有选举其他server的时候,并且选举RPC中的candidate c的term大于或等于follower的term时,才会返回选举当前candidate c为主,否则,则返回拒绝选举当前candidate c为主。

对于leader的心跳RPC,如果leader的心跳的term大于或等于follower的term,则认可该leader的心跳,否则,不认可该leader的心跳。

代码实现

candidate状态下的选主功能

根据前面描述,主要的逻辑为

  • 等待选举超时
  • 增加term,置状态为follower,并且选举自己为leader
  • 向其他的server并行地发送选举RPC,直到碰到上述描述的三种情况退出
func (rf *Raft) election_one_round() bool {
    // begin election
    var timeout int64
    var done int
    var triggerHeartbeat bool
    timeout = 100
    last := milliseconds()
    success := false
    rf.mu.Lock()
    rf.becomeCandidate()
    rf.mu.Unlock()
    printTime()
    rpcTimeout := 20
    fmt.Printf("candidate=%d start electing leader\n", rf.me)
    for {
        for i := 0; i < len(rf.peers); i++ {
            if i != rf.me {
                var args RequestVoteArgs
                server := i
                args.Term = rf.currentTerm
                args.CandidateId = rf.me
                var reply RequestVoteReply
                printTime()
                fmt.Printf("candidate=%d send request vote to server=%d\n", rf.me, i)
                go rf.sendRequestVoteAndTrigger(server, args, &reply, rpcTimeout)
            }
        }
        done = 0
        triggerHeartbeat = false
        for i := 0; i < len(rf.peers)-1; i++ {
            printTime()
            fmt.Printf("candidate=%d waiting for select for i=%d\n", rf.me, i)
            select {
            case ok := <-rf.electCh:
                if ok {
                    done++
                    success = done >= len(rf.peers)/2 || rf.currentLeader > -1
                    success = success && rf.votedFor == rf.me
                    if success && !triggerHeartbeat {
                        triggerHeartbeat = true
                        rf.mu.Lock()
                        rf.becomeLeader()
                        rf.mu.Unlock()
                        rf.heartbeat <- true
                        printTime()
                        fmt.Printf("candidate=%d becomes leader\n", rf.currentLeader)
                    }
                }
            }
            printTime()
            fmt.Printf("candidate=%d complete for select for i=%d\n", rf.me, i)
        }
        if (timeout+last < milliseconds()) || (done >= len(rf.peers)/2 || rf.currentLeader > -1) {
            break
        } else {
            select {
            case <-time.After(time.Duration(10) * time.Millisecond):
            }
        }
    }
    printTime()
    fmt.Printf("candidate=%d receive votes status=%t\n", rf.me, success)
    return success
}

首先等待选举超时,超时后,会进入真正的选举逻辑election_one_round()

首先,进入candidate状态,增加其term,然后,选举自己。

func (rf *Raft) becomeCandidate() {                                                                                                                                                     
  rf.state = 1   
  rf.setTerm(rf.currentTerm + 1)
  rf.votedFor = rf.me
  rf.currentLeader = -1
}

接着,向除自己外的server发送选举RPC,等待server的回复

fmt.Printf("candidate=%d start electing leader\n", rf.me)
    for {
        for i := 0; i < len(rf.peers); i++ {
            if i != rf.me {
                var args RequestVoteArgs
                server := i
                args.Term = rf.currentTerm
                args.CandidateId = rf.me
                var reply RequestVoteReply
                printTime()
                fmt.Printf("candidate=%d send request vote to server=%d\n", rf.me, i)
                go rf.sendRequestVoteAndTrigger(server, args, &reply, rpcTimeout)
            }
        }
        done = 0
        triggerHeartbeat = false
        for i := 0; i < len(rf.peers)-1; i++ {
            printTime()
            fmt.Printf("candidate=%d waiting for select for i=%d\n", rf.me, i)
            select {
            case ok := <-rf.electCh:
                if ok {
                    done++
                    success = done >= len(rf.peers)/2 || rf.currentLeader > -1
                    success = success && rf.votedFor == rf.me
                    if success && !triggerHeartbeat {
                        triggerHeartbeat = true
                        rf.mu.Lock()
                        rf.becomeLeader()
                        rf.mu.Unlock()
                        rf.heartbeat <- true
                        printTime()
                        fmt.Printf("candidate=%d becomes leader\n", rf.currentLeader)
                    }
                }
            }
            printTime()
            fmt.Printf("candidate=%d complete for select for i=%d\n", rf.me, i)
        }
        if (timeout+last < milliseconds()) || (done >= len(rf.peers)/2 || rf.currentLeader > -1) {
            break
        } else {
            select {
            case <-time.After(time.Duration(10) * time.Millisecond):
            }
        }
    }
    printTime()
    fmt.Printf("candidate=%d receive votes status=%t\n", rf.me, success)
    return success

当成功返回数目到多数派时(包含自己在内),则宣布自己称为leader,即becomeLeader(),如下

func (rf *Raft) becomeLeader() {
    rf.state = 2
    rf.currentLeader = rf.me
}

即,修改自身状态为leader。然后,给发送心跳的线程发送 rf.heartbeat <-true,通知心跳线程开始发心跳包。

leader状态下的广播心跳功能

首先,来看触发心跳的逻辑

func (rf *Raft) sendLeaderHeartBeat() {
  timeout := 20
  for {   
    select {
    case <-rf.heartbeat:
      rf.sendAppendEntriesImpl()
    case <-time.After(time.Duration(timeout) * time.Millisecond):
      rf.sendAppendEntriesImpl()
    }     
  }       
}

分为两个方面:

  • 第一个为刚当选为leader后,需要马上发送心跳信息,防止新的选举发生

  • 第二个是leader周期性的发送心跳信息,来宣布自己为主

真正的广播心跳的逻辑如下:

func (rf *Raft) sendAppendEntriesImpl() {
    if rf.currentLeader == rf.me {
        var args AppendEntriesArgs
        var success_count int
        timeout := 20
        args.LeaderId = rf.me
        args.Term = rf.currentTerm
        printTime()
        fmt.Printf("broadcast heartbeat start\n")
        for i := 0; i < len(rf.peers); i++ {
            if i != rf.me {
                var reply AppendEntriesReply
                printTime()
                fmt.Printf("Leader=%d send heartbeat to server=%d\n", rf.me, i)
                go rf.sendHeartBeat(i, args, &reply, timeout)
            }
        }
        for i := 0; i < len(rf.peers)-1; i++ {
            select {
            case ok := <-rf.heartbeatRe:
                if ok {
                    success_count++
                    if success_count >= len(rf.peers)/2 {
                        rf.mu.Lock()
                        rf.setMessageTime(milliseconds())
                        rf.mu.Unlock()
                    }
                }
            }
        }
        printTime()
        fmt.Printf("broadcast heartbeat end\n")
        if success_count < len(rf.peers)/2 {
            rf.mu.Lock()
            rf.currentLeader = -1
            rf.mu.Unlock()
        }
    }
}

先是向集群中所有的其他server广播心跳,分为两种结果:

  • 收到了大多数server的确认,则更新leader的超时时间,防止重新进入选举状态

  • 未收到大多数server的确认,则会退出发送心跳的逻辑,即置currentLeader = -1,此后,自然会有选举超时的server重新发起选举

follower状态下的确认功能

包括对选举RPC的确认已经对心跳RPC的确认。

选举RPC的确认逻辑如下

func (rf *Raft) RequestVote(args RequestVoteArgs, reply *RequestVoteReply) {
  // Your code here.
  currentTerm, _ := rf.GetState()
  if args.Term < currentTerm {
    reply.Term = currentTerm
    reply.VoteGranted = false
    printTime() 
    fmt.Printf("candidate=%d term = %d smaller than server = %d, currentTerm = %d\n", args.CandidateId, args.Term, rf.me, rf.currentTerm)
    return      
  }             

  if rf.votedFor != -1 && args.Term <= rf.currentTerm {
    reply.VoteGranted = false
    rf.mu.Lock()
    rf.setTerm(max(args.Term, currentTerm))
    reply.Term = rf.currentTerm
    rf.mu.Unlock()
    printTime() 
    fmt.Printf("rejected candidate=%d term = %d server = %d, currentTerm = %d, has_voted_for = %d\n", args.CandidateId, args.Term, rf.me, rf.currentTerm, rf.votedFor)
  } else {      
    rf.mu.Lock()
    rf.becomeFollower(max(args.Term, currentTerm), args.CandidateId)
    rf.mu.Unlock()
    reply.VoteGranted = true
    fmt.Printf("accepted server = %d voted_for candidate = %d\n", rf.me, args.CandidateId)
  }             
}

如果当前server的term大于candidate的term,或者当前server已经选举过其他server为leader了,那么返回拒绝的RPC,否则,则返回成功的RPC,并置自身状态为follower。

心跳的RPC的逻辑如下

func (rf *Raft) AppendEntries(args AppendEntriesArgs, reply *AppendEntriesReply) {
  if args.Term < rf.currentTerm {
    reply.Success = false
    reply.Term = rf.currentTerm
  } else {       
    reply.Success = true
    reply.Term = rf.currentTerm
    rf.mu.Lock() 
    rf.currentLeader = args.LeaderId
    rf.votedFor = args.LeaderId
    rf.state = 0 
    rf.setMessageTime(milliseconds())
    printTime()  
    fmt.Printf("server = %d learned that leader = %d\n", rf.me, rf.currentLeader)
    rf.mu.Unlock()
  }              
}

如果follower的term大于leader的term,则返回拒绝的RPC,否则,返回成功的RPC。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值