Raft源码学习
背景
看本篇博客,需要先对Raft协议本身有了解。然后再开始看源码,会有更深入的体会。不建议对Raft协议没有任何的认识就可以直接开始看本篇文章。Raft协议的中英文论文已经有相关比较好的博客。参考引用。
我看的是githab的Raft代码。分析这个代码是如何实现Raft协议的。
体会
看完算法,再去看源码的实现。功能就真的是按照算法一比一去实现的。这是我第一次看源码如此轻松。读源码就像是读小说的感觉一样。感觉还是得看论文可以最快的认识一个算法和协议。全文没有一句废话。
代码分析过程
整个项目的核心代码在Raft.go里面。我们根据Raft协议本身的一些情况。然后对照代码分析。
Leader选举过程分析,三种状态转化
// run is a long running goroutine that runs the Raft FSM.
func (r *Raft) run() {
for {
// Check if we are doing a shutdown
select {
case <-r.shutdownCh:
// Clear the leader to prevent forwarding
r.setLeader("")
return
default:
}
// Enter into a sub-FSM、
// 循环判断状态,是具体的状态,执行具体的逻辑
switch r.getState() {
case Follower:
r.runFollower()
case Candidate:
r.runCandidate()
case Leader:
r.runLeader()
}
}
}
r.runFollower 方法分析
Raft使用心跳机制来触发leader选举。当server启动的时候是处于follower状态,当它可以收到来自leader或者candidate的有效RPC请求时就会保持follower的状态。Leader发送周期性的心跳(不含日志的AppendEntries RPC)给所有的follower来确保自己的权威。如果一个follower一段时间(称为election timeout)没有收到消息,它就会假定leader失效并开始新的选举。
为了开始新一轮选举,follower会提高自己当前的term并转为candidate状态
// runFollower runs the FSM for a follower.
func (r *Raft) runFollower() {
didWarn := false
r.logger.Info("entering follower state", "follower", r, "leader", r.Leader())
metrics.IncrCounter([]string{"raft", "state", "follower"}, 1)
heartbeatTimer := randomTimeout(r.conf.HeartbeatTimeout)
for r.getState() == Follower {
select {
case rpc := <-r.rpcCh:
r.processRPC(rpc)
case c := <-r.configurationChangeCh:
// Reject any operations since we are not the leader
c.respond(ErrNotLeader)
... //省略部分不重要代码,都是不需要处理的Reponse
case <-heartbeatTimer:
// Restart the heartbeat timer
heartbeatTimer = randomTimeout(r.conf.HeartbeatTimeout)
// Check if we have had a successful contact
lastContact := r.LastContact()
if time.Now().Sub(lastContact) < r.conf.HeartbeatTimeout {
continue
}
// Heartbeat failed! Transition to the candidate state
lastLeader := r.Leader()
r.setLeader("")
if r.configurations.latestIndex == 0 {
if !didWarn {
r.logger.Warn("no known peers, aborting election")
didWarn = true
}
} else if r.configurations.latestIndex == r.configurations.committedIndex &&
!hasVote(r.configurations.latest, r.localID) {
if !didWarn {
r.logger.Warn("not part of stable configuration, aborting election")
didWarn = true
}
} else {
//心跳超时了,设置自己为Candidate。并退出Follower的循环
r.logger.Warn("heartbeat timeout reached, starting election", "last-leader", lastLeader)
metrics.IncrCounter([]string{"raft", "transition", "heartbeat_timeout"}, 1)
r.setState(Candidate)
return
}
case <-r.shutdownCh:
return
}
}
}
看一下 randomTimeout方法。这里是心跳定时的逻辑
// randomTimeout returns a value that is between the minVal and 2x minVal.
func randomTimeout(minVal time.Duration) <-chan time.Time {
if minVal == 0 {
return nil
}
extra := (time.Duration(rand.Int63()) % minVal)
return time.After(minVal + extra)
}
r.runCandidate 方法分析
为了开始新一轮选举,follower会提高自己当前的term并转为candidate状态。它会先给自己投一票然后并行向集群中的其他server发出RequestVote RPC,candidate会保持这个状态,直到下面三种事情之一发生:
(a) 赢得选举。当candidate收到了集群中相同term的多数节点的赞成票时就会选举成功,每一个server在给定的term