看参考贴大佬们都是一两天就把lab2a做出来了,我用了两周(哭晕在厕所),知道自己菜,没想到能菜到这种程度。
之前一直参考一篇简单的经验贴,有代码结构,没有锁,没有term改变,上手是很快就上手了,但是事后全是坑,调了两周,哎。(找经验贴一定要找全代码的,而且要看有过了测试结果的),但是确实是让我对代码的整体结构有了了解。
最后一天换了一篇经验贴参考,一天完事。撒花
lab2a我觉得三大难点:锁,整体架构,细节多
1 参考资料
2 代码
- 首先是用到的结构体(或参数,分为4部分):
//1: 选举心跳时间
var HeartBeatTimeout = 100 * time.Millisecond
//2: raft结构
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 *raft.Persister // Object to hold this peer's persisted state
me int // this peer's index into peers[]
dead int32 // set by Kill()
status string //状态
term int //第几轮投票
voteFor int //为谁投票,-1表示还没投票
voteCount int //获得总票数,初始为0
overtime time.Duration //任期倒计时总长
timer *time.Ticker //实现倒计时功能
// Your data here (2A, 2B, 2C).
// Look at the paper's Figure 2 for a description of what
// state a Raft server must maintain.
}
//3:RequestVote的参数
type RequestVoteArgs struct {
// Your data here (2A, 2B).
Term int
CandidateId int
}
type RequestVoteReply struct {
// Your data here (2A).
Term int
VoteGranted bool
}
//4: AppendEntries的参数
type AppendEntriesArgs struct {
Term int
LeaderId int
}
type AppendEntriesReply struct {
Term int
Success bool
}
这里的倒计时是用time.Ticker+select语句实现的(实验里说不建议),不太了解的百度先看一下
- 然后是入口函数make()
func Make(peers []*labrpc.ClientEnd, me int,
persister *raft.Persister, applyCh chan ApplyMsg) *Raft {
rf := &Raft{}
rf.peers = peers
rf.persister = persister
rf.me = me
// Your initialization code here (2A, 2B, 2C)
rf.status = "follower"
rf.term = 0
//初始化选举时间倒计时
rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond
rf.timer = time.NewTicker(rf.overtime)
rf.voteFor = -1
rf.voteCount = 0
// initialize from state persisted before a crash
//rf.readPersist(persister.ReadRaftState())
// start ticker goroutine to start elections
go rf.ticker()
return rf
}
- 然后是ticker()函数:
func (rf *Raft) ticker() {
time.Sleep(1 * time.Millisecond) //让测试标题输出比这里面的输出早
for rf.killed() == false {
// Your code here to check if a leader election should
// be started and to randomize sleeping time using
// time.Sleep().
select {
case <-rf.timer.C:
rf.mu.Lock()
//(坑1)defer rf.mu.Unlock() 这样写,一直无法释放锁,应该写到select 语句最后
switch rf.status {
//1: follower先将自己的状态变为candidate,
case "follower":
rf.status = "candidate"
fallthrough
case "candidate":
//2 然后为为自己投票,总票数加1,
rf.term++
rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond
rf.timer.Reset(rf.overtime)
rf.voteFor = rf.me
rf.voteCount = 1 //(坑2)rf.voteCount++错误,进入下一轮的candidate会在原来基础上加1
fmt.Printf("(轮数:%d)candidate!!! 第%d号任务已经进入candidate状态\n", rf.term, rf.me)
for i := 0; i < len(rf.peers); i++ {
if i == rf.me {
continue
}
reply := RequestVoteReply{}
fmt.Printf("发起投票! 第%d号任务(term:%d)向第%d号任务发起投票\n", rf.me, rf.term, i)
//以线程的方式发起投票,前面参考文献里面关于锁那个部分提到了
go rf.sendRequestVote(i, &RequestVoteArgs{Term: rf.term, CandidateId: rf.me}, &reply)
}
//3: leader 发起心跳
case "leader":
rf.timer.Reset(HeartBeatTimeout)
for j, _ := range rf.peers {
if j == rf.me {
continue
}
reply := AppendEntriesReply{}
go rf.sendAppendEntries(j, &AppendEntriesArgs{Term: rf.term, LeaderId: rf.me}, &reply)
}
}
rf.mu.Unlock()
}
}
}
这里的锁的释放是个小细节,一定要放在select语句后面,而不是直接
defer rf.mu.Unlock()
否则会让ticker()函数一直放不了锁,导致其他线程无法跑起来。在第三部分我会贴出我的bug截图
- 请求投票函数sendRequestVote():
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
//一直发送请求,直到成功
ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
for ok == false {
ok = rf.peers[server].Call("Raft.RequestVote", args, reply)
}
//一把大锁
rf.mu.Lock()
defer rf.mu.Unlock()
//情况1: 收到过期的RPC回复,不处理
//(这里很重要,因为可能发送了多次rpc,但是因为网络延迟导致每收到及时的rpc回复,等到下一轮选举投票的时候却收到了)
if args.Term < rf.term {
return false
}
//情况2: 允许投票,
if reply.VoteGranted == true {
fmt.Printf(" 同意投票! %d号任务(term:%d) 同意给 %d号任务(term:%d) 投票\n", server, rf.term, rf.me, reply.Term)
if rf.voteCount < len(rf.peers)/2 {
rf.voteCount++
}
if rf.voteCount >= len(rf.peers)/2 {
fmt.Printf("新leader!! 第%d号任务已经有选票%d,已经进入leader状态\n", rf.me, rf.voteCount)
rf.status = "leader"
rf.timer.Reset(HeartBeatTimeout)
}
} else {
//情况3: 不允许投票,
fmt.Printf(" 拒绝投票! %d号任务(term:%d) 拒绝给 %d号任务(term:%d) 投票\n", server, rf.term, rf.me, reply.Term)
if reply.Term > rf.term {
rf.status = "follower"
rf.term = reply.Term
rf.voteFor = -1
rf.voteCount = 0
rf.timer.Reset(time.Duration(150+rand.Intn(200)) * time.Millisecond)
}
}
return true
}
//情况1: 收到过期的RPC回复,不处理
if args.Term < rf.term { return false }
这一句代码要好好理解
因为测试2,3会将raft断开,所以请求RequestVote得不到回复,raft恢复后,旧的请求RequestVote可能会得到回复,这类回复直接抛弃-
- 第一个rpc函数RequestVote():
// example RequestVote RPC handler.
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
// Your code here (2A, 2B).
//一把大锁
rf.mu.Lock()
defer rf.mu.Unlock()
//fmt.Printf("收到投票请求! %d号任务(term:%d)收到来自%d号任务(term:%d)\n", rf.me, rf.term, args.CandidateId, args.Term)
//默认不同意投票
reply.VoteGranted = false
reply.Term = rf.term
//情况1: 请求投票的raft的term太小,不给投票
if args.Term < rf.term {
return
}
//情况2: 请求投票的raft的term大 将自己的term更新,如果是自己是candidate状态,转为follower
if args.Term > rf.term {
rf.term = args.Term
//这里本来有投票的代码实现,放在了下面的情况3一起处理
rf.voteFor = -1
rf.voteCount = 0
if rf.status == "candidate" {
rf.status = "follower"
}
}
//情况3: 请求投票的raft的term和自己相等情况下,如果rf.voteFor == -1,则投票
if rf.voteFor == -1 || rf.voteFor == args.CandidateId {
reply.VoteGranted = true
rf.term = args.Term
rf.voteFor = args.CandidateId
rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond
rf.timer.Reset(rf.overtime)//重新设置投票倒计时
}
}
这里的
rf.voteFor == args.CandidateId
条件,是论文中给出,删除也可以过测试放在这里,我猜测是为了处理,同一个raft请求了多次该raft投票,但是第一次的回复因为网络原因导致,请求raft没有收到,所以重发的请求。
这里临时想到一个问题:重复发送的投票请求,若请求raft先后都收到了回复,那么应该记一票,去重处理代码里没有做!(有机会再做吧,现在的思路:做一个标识的数组来标识已经收到的raft回复)
- 发送心跳函数 sendAppendEntries():
func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {
//ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
//return ok
//一直请求到成功
ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
for ok == false {
ok = rf.peers[server].Call("Raft.AppendEntries", args, reply)
}
//一把大锁
rf.mu.Lock()
defer rf.mu.Unlock()
//情况1: 收到过期的rpc回复
if args.Term < rf.term {
return false
}
//情况2: 心跳不允许
if reply.Success == false {
if reply.Term > rf.term {
rf.status = "follower"
rf.term = reply.Term
rf.voteFor = -1
rf.voteCount = 0
rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond
rf.timer.Reset(rf.overtime)
}
}
fmt.Printf("心跳回复! %d号任务给%d号任务发送心跳,结果:%v\n", rf.me, server, reply.Success)
return true
}
//情况1: 收到过期的rpc回复 if args.Term < rf.term { return false }
这里和sendRequestVote()函数一样,丢弃过期的rpc回复
- 第二个rpc函数 AppendEntries():
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
// Your code here (2A, 2B).
// fmt.Printf("收到心跳! %d号任务(term:%d)收到来自%d号任务(term:%d)的心跳!,\n", rf.me, rf.term, args.LeaderId, args.Term)
//情况1: 收到的rpc的term太旧
if args.Term < rf.term {
reply.Term = rf.term
reply.Success = false
} else {
//情况2: 收到的rpc的term 比自己的term大或相等
rf.status = "follower"
rf.term = args.Term
reply.Success = true
rf.term = args.Term
rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond
rf.timer.Reset(rf.overtime)
rf.voteCount = 0
rf.voteFor = -1
}
}
- 最后一个函数GetState():测试中要用到
func (rf *Raft) GetState() (int, bool) {
rf.mu.Lock()
defer rf.mu.Unlock()
var term int
var isleader bool
// Your code here (2A).
term = rf.term
isleader = rf.status == "leader"
return term, isleader
}
3 bug解决
程序跑的时候,出现下列情况
- 任务1没有经过第一轮直接进入了第二轮投票
原因:0号任务给1号发起投票后,这时候,0号任务的term是1,而1号任务term是0,直接把1号任务的term变成了1。1号任务再进入candidate就变成了2
解决:正常情况不是bug
- 1号任务已经执行了requestVote() 但是,0号任务并没有记上票,对sendRequestVote() 打断点,发现进不到执行对reply操作的代码,
原因:可能是sendRequestVote发生了死锁,
解决:
select { case <-rf.timer.C: //2 follower先将自己的状态变为candidate,然后为为自己投票,总票数加1, rf.mu.Lock() defer rf.mu.Unlock() switch rf.status { case "follower": rf.status = "candidate" fallthrough case "candidate": rf.term++ rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond rf.timer.Reset(rf.overtime) rf.voteFor = rf.me rf.voteCount = 1 //rf.voteCount++错误,率先进入第三轮选举就会被当选 fmt.Printf("(轮数:%d)candidate!!! 第%d号任务已经进入candidate状态\n", rf.term, rf.me) for i := 0; i < len(rf.peers); i++ { if i == rf.me { continue } reply := RequestVoteReply{} fmt.Printf("发起投票! 第%d号任务(term:%d)向第%d号任务发起投票\n", rf.me, rf.term, i) go rf.sendRequestVote(i, &RequestVoteArgs{Term: rf.term, CandidateId: rf.me}, &reply) } case "leader": rf.timer.Reset(HeartBeatTimeout) for j, _ := range rf.peers { if j == rf.me { continue } reply := AppendEntriesReply{} go rf.sendAppendEntries(j, &AppendEntriesArgs{Term: rf.term, LeaderId: rf.me}, &reply) } } }
这里的
defer rf.mu.Unlock()
写在了for rf.killed() == false
里,导致主线程一直没有释放锁,解决:
rf.mu.Unlock()
把这个写在select 语句结束select { case <-rf.timer.C: //2 follower先将自己的状态变为candidate,然后为为自己投票,总票数加1, rf.mu.Lock() //defer rf.mu.Unlock() 这样写,一直无法释放锁,应该写到select 语句最后 switch rf.status { case "follower": rf.status = "candidate" fallthrough case "candidate": rf.term++ rf.overtime = time.Duration(150+rand.Intn(200)) * time.Millisecond rf.timer.Reset(rf.overtime) rf.voteFor = rf.me rf.voteCount = 1 //rf.voteCount++错误,率先进入第三轮选举就会被当选 fmt.Printf("(轮数:%d)candidate!!! 第%d号任务已经进入candidate状态\n", rf.term, rf.me) for i := 0; i < len(rf.peers); i++ { if i == rf.me { continue } reply := RequestVoteReply{} fmt.Printf("发起投票! 第%d号任务(term:%d)向第%d号任务发起投票\n", rf.me, rf.term, i) go rf.sendRequestVote(i, &RequestVoteArgs{Term: rf.term, CandidateId: rf.me}, &reply) } case "leader": rf.timer.Reset(HeartBeatTimeout) for j, _ := range rf.peers { if j == rf.me { continue } reply := AppendEntriesReply{} go rf.sendAppendEntries(j, &AppendEntriesArgs{Term: rf.term, LeaderId: rf.me}, &reply) } } rf.mu.Unlock() }
4 贴个测试结果
-
测试1:
-
测试2:
- 测试3:
5 反思
lab2a对锁的使用,让我着实学到了不少。几乎踩完所有坑后,才看到官方文档里有关于锁的建议,然后觉得总结得是真的好,我全踩了一遍。
明天继续干lab2b说是比lab2a还酸爽。奥利给!!!