本文假设你已经对课程进行了部分了解,所以不对课程的要求进行过多描述,重点在代码的分析和一些踩坑记录和理解。
课程链接: nil.csail.mit.edu/6.824/2020/…
目标
实现raft算法中的领导人选举部分
需要实现的部分
-
Make()
-
Raft{}
-
GetState()
-
RequestVote()
-
RequestVoteArgs{}
-
RequestVoteReply{}
-
AppendEntries()
-
AppendEntriesArgs {}
-
AppendEntriesReply {}
流程图
理解了流程图,要实现代码就比较简单了

code
这里直接展示代码,代码的逻辑配合流程图可以理解清楚。
Raft { }
const Leader int32 = 1
const Follower int32 = 2
const Candidate int32 = 3
type Raft struct {
mu sync.Mutex // Lock to protect shared access to this Peer's state
peers []*labrpc.ClientEnd // RPC end points of all peers
persister *Persister // Object to hold this Peer's persisted state
me int // this Peer's index into peers[]
dead int32 // set by Kill()
//任期号
Term int32
//获得选票的服务器
VotedFor int
//角色类型
Role int32
//下一个超时时间
NextTimeout time.Time
}
Make()
最先要实现的部分是Make()方法,目的是要创建一个Raft对象、运行后台任务
func Make(peers []*labrpc.ClientEnd, me int, persister *Persister, applyCh chan ApplyMsg) *Raft {
//log.Printf("== Make() ==")
rf := &Raft{}
rf.peers = peers
rf.persister = persister
rf.me = me
// Your initialization Code here (2A, 2B, 2C).
rf.Term = 0
rf.Role = Follower
rf.VotedFor = -1
// 设置下次心跳检查时间
rf.updateHeartbeatTime()
// 维持状态的协程
go rf.maintainStateLoop()
// initialize from state persisted before a crash
rf.readPersist(persister.ReadRaftState())
return rf
}
goroutine 的方法 maintainStateLoop() 负责不停的执行各个角色对应的操作
func (rf *Raft) maintainStateLoop() {
for !rf.killed() {
rf.mu.Lock()
if rf.isLeader() {
rf.maintainsLeader()
} else if rf.isFollower() {
rf.maintainsFollower()
} else if rf.isCandidate() {
rf.maintainsCandidate()
} else {
log.Fatalf("[raft-%v %v] Role type error : %v \n", rf.me, rf.getRole(), rf.Role)
}
}
}
follower 的职责
判断心跳是否超时,如果超时就变成candidate
func (rf *Raft) maintainsFollower() {
if rf.heartbeatTimeout() {
rf.changeToCandidate()
rf.mu.Unlock()
} else {
rf.mu.Unlock()
time.Sleep(10 * time.Millisecond)
}
}
candidate 的职责
负责发起竞选的rpc,根据请求的reply进行处理不同的处理
func (rf *Raft) maintainsCandidate() {
// 检查超时
if !rf.heartbeatTimeout() {
rf.mu.Unlock()
time.Sleep(10 * time.Millisecond)
return
}
// 发送投票RPC
log.Printf("[raft-%v %v %v] == 发起投票RPC == \n", rf.me, rf.getRole(), rf.Term)
rf.VotedFor = rf.me
rf.Term += 1
maxTerm, sumVotes := rf.Term, len(rf.peers)
voteReply := make(chan *RequestVoteReply)
args := RequestVoteArgs{Peer: rf.me, Term: rf.Term}
for idx := range rf.peers {
if rf.me == idx {
continue
}
go func(from int, to int, term int32) {
reply := RequestVoteReply{Peer: to, Term: term, VoteGranted: false}
_ = rf.sendRequestVote(to, &args, &reply)
voteReply <- &reply
}(rf.me, idx, rf.Term)
}
rf.mu.Unlock()
// 处理投票回复
validVotes, acceptVotes := 1, 1
select {
case reply := <-voteReply:
validVotes++
if reply.VoteGranted {
acceptVotes++
} else if reply.Term > maxTerm {
maxTerm = reply.Term
}
if validVotes > sumVotes/2 {
goto VotedDone
}
}
//汇总投票结果
VotedDone:
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.killed() || !rf.isCandidate() {
} else if maxTerm > rf.Term {
log.Printf("[raft-%v-%v-%v] 投票结果: 有更大的Term,放弃竞选: %v.\n", rf.me, rf.getRole(), rf.Term, maxTerm)
rf.changeToFollower(maxTerm)
} else if rf.isQuorum(acceptVotes) {
log.Printf("[raft-%v-%v-%v] == 投票通过: 总票数 = %v. 赞同票数 = %v == \n", rf.me, rf.getRole(), rf.Term, sumVotes, acceptVotes)
rf.changeToLeader()
} else {
log.Printf("[raft-%v-%v-%v] == 投票未通过: 总票数 = %v. 赞同票数 = %v == \n", rf.me, rf.getRole(), rf.Term, sumVotes, acceptVotes)
rf.updateHeartbeatTime()
}
}
func (rf *Raft) isQuorum(accept int) bool {
return accept > len(rf.peers)/2
}
Leader 的职责
负责向其他所有节点发送心跳
func (rf *Raft) maintainsLeader() {
//log.Printf("[raft-%v %v %v] 发送心跳RPC. now = %v", rf.me, rf.getRole(), rf.Term, time.Now().Local())
args := AppendEntriesArgs{Peer: rf.me, Term: rf.Term}
for idx := range rf.peers {
if idx == rf.me {
continue
}
go func(from int, to int, term int32) {
reply := AppendEntriesReply{}
flag := rf.sendAppendEntries(to, &args, &reply)
rf.mu.Lock()
defer rf.mu.Unlock()
if !rf.killed() && rf.isLeader() && flag && reply.Term > rf.Term && args.Term == rf.Term {
rf.changeToFollower(reply.Term)
}
}(rf.me, idx, rf.Term)
}
rf.mu.Unlock()
time.Sleep(100 * time.Millisecond)
}
竞选投票相关
type RequestVoteArgs struct {
// 竞选人id
Peer int
// 竞选人term
Term int32
}
type RequestVoteReply struct {
// 发送回复的节点id
Peer int
// 发送回复的节点term
Term int32
// 如果为true表示赞同
VoteGranted bool
}
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.killed() {
return
}
log.Printf("[raft-%v %v %v] 处理%v的投票RPC. T = %v \n", rf.me, rf.getRole(), rf.Term, args.Peer, args.Term)
reply.Term, reply.VoteGranted = rf.Term, false
// term太小,不理
if args.Term < rf.Term {
return
}
// 更大的term
if args.Term > rf.Term {
rf.changeToFollower(args.Term)
}
// 每个任期,只能投票一次
if rf.VotedFor == -1 || rf.VotedFor == args.Peer {
reply.VoteGranted = true
rf.VotedFor = args.Peer
rf.updateHeartbeatTime()
}
}
心跳相关
type AppendEntriesArgs struct {
Peer int
Term int32
}
type AppendEntriesReply struct {
Term int32
Result bool
}
\
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.killed() {
return
}
//log.Printf("[raft-%v %v %v] 接收来自%v的心跳请求. T = %v.\n", rf.me, rf.getRole(), rf.Term, args.Peer, args.Term)
reply.Term, reply.Result = rf.Term, args.Term >= rf.Term
if args.Term < rf.Term {
return
} else if rf.isCandidate() {
rf.changeToFollower(args.Term)
} else {
rf.updateHeartbeatTime()
}
// 日志操作lab-2A不实现
rf.persist()
}
注意事项
--- FAIL: TestReElection2A (8.51s)
config.go:330: expected one leader, got none
在这个test case中,先disconnect已经选出来的leader,然后给2s的时间选举出新的leader,这里的错误原因就是2s时间没有选举出新的leader。
要点在于如何提高选举的效率,保证更快的时间内(2s内)选举出leader,
出现这个情况的原因大概率和活锁有关,两个或多个candidate争夺竞选,这里注意一些要点:
- 关于选举间隔时间:建议 150 ~ 300 之间
-
设置太高会浪费空闲时间
-
设置太低又容易发生活锁,也存在一种情况就是leader选举出来了,但是又收到term更高的vote rp请求。
-
当选leader,应该尽快发送心跳rpc,防止follow过期
-
leader 发送心跳的频率 , 建议在100ms一次,官方也是建议1s不超过10次
关于更新心跳时间的条件,首先本质上就是在收到有效的rpc请求时(知道有别人存活)或者自己发起选举时(知道自己存活),就要更新心跳时间,如下三个点:
-
收到leader的心跳rpc时(leader.term >= self.term 即算有效)
-
收到candidate的投票请求,并投了赞同票时
-
在进行新一轮的选举时
脚本测试
测试脚本: gist.github.com/jonhoo/
250 次测试结果通过

Reference
-
参考使用chan + select 的模式: yuerblog.cc/2020/08/13/…
-
raft论文(中文) : yuerblog.cc/wp-content/… [ 主要看5.2 leader 选举部分 ]
-
一些注意事项 : zhuanlan.zhihu.com/p/368433074