MIT-6.824 Raft lab2A
前言
本篇文章将介绍Raft 2A部分的实验内容以及其中包含的一些知识点,在paper、参考资料以及他人博客的帮助下,用了两天时间将2A部分顺利完成,其中还是历经了一些艰辛的。
前期准备
完成这个lab2A部分之前,共做了下列这些准备
- 观看MIT6.824 Raft视频P6,P7视频,P6视频观看笔记,P7视频观看笔记(没有经过系统整理,看到哪总结到哪了)
- 阅读RaftPaper,Paper思维导图
- 进行lab2的实验要求阅读,lab2实验网址
- 进行Raft的一个可视化过程观看,加深理解raft过程可视化
- 以及在一开始拿到代码无从下手时,阅读大佬们的博客
实验过程
接下来便开始leader选举这一部分的代码实现了,leader选举的细节如下图所示。
接下来便是根据流程一步步进行代码实现了。
首先是Raft结构体的定义,Raft结构体的定义在Raft Paper中绝大部分均已给出,但在lab2A的实现中额外增加了4个参数,来辅以实现。
//
// A Go object implementing a single Raft peer.
//
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()
currentTerm int
votedFor int
logBuffer []LogEntry
commitIndex int
lastApplied int
nextIndex []int
matchIndex []int
role Role // 角色
heartbeat time.Duration // 心跳时间
electiontimeout time.Duration // 选举时间
timer *time.Timer //定时器
applyCh chan ApplyMsg
}
其他的参数在论文中均给出了详细的解释,这里主要讲一下关于时间的三个参数(也是在最初比较困惑我的地方),额外的三个参数分别为心跳时间、选举时间还有定时器。
我们的选举过程是通过定时器来进行超时触发的,也就意外着我们需要有一个选举时间,除此之外,我们在leader选举完成后,leader需要通过心跳发送给其他服务器,以告诉其他服务器“我是leader,做准备吧”,因此还需要一个心跳时间。
这两个时间是不同的时间,同样,对应于不同的角色,使用也不相同,心跳时间是对leader这一角色才有的时间,因为只有leader才有主动发送消息(LogEntry)的资格,而选举时间则对于follower或者candidate而言,超时没收到消息后将会进行选举。
因此,无论是心跳还是选举,它们的机制均为定时触发,这也就是timer这一参数的作用,timer的功能变为一个定时器,通过给它设置不同的时间,它将进行定时触发。
具体使用方法如下图所示,通过select,case<-关键字来辅助当时间到了便会进行相应功能的触发。
// The ticker go routine starts a new election if this peer hasn't received
// heartsbeats recently.
func (rf *Raft) ticker() {
for rf.killed() == false {
select {
// 定时器触发
case <-rf.timer.C:
if rf.killed() {
return
}
rf.mu.Lock()
switch rf.role {
case ROLE_FOLLOWER:
rf.role = ROLE_CANDIDATE
fallthrough
case ROLE_CANDIDATE:
MyPrint(rf, "begin to leaderSelect")
rf.leaderSelect()
case ROLE_LEADER:
rf.timer.Reset(rf.heartbeat)
// 2A暂不需要实现
}
rf.mu.Unlock()
}
}
}
以上便是参数以及定时器的介绍。
Raft代码将通过make函数进入,进行serve的赋值及执行,这一过程我们可以在test_test.go中进行发现。
make函数主要完成初始化的操作。
func Make(peers []*labrpc.ClientEnd, me int,
persister *Persister, applyCh chan ApplyMsg) *Raft {
rf := &Raft{}
rf.peers = peers
rf.persister = persister
rf.me = me
rf.commitIndex = -1
rf.currentTerm = 0
rf.lastApplied = -1
rf.votedFor = -1
rf.logBuffer = make([]LogEntry, 0)
rf.matchIndex = make([]int, len(peers))
rf.nextIndex = make([]int, len(peers))
rf.role = ROLE_FOLLOWER
rf.heartbeat = 100 * time.Millisecond
rf.resetElectionTimer()
// Your initialization code here (2A, 2B, 2C).
rf.applyCh = applyCh
// initialize from state persisted before a crash
rf.readPersist(persister.ReadRaftState())
// start ticker goroutine to start elections
go rf.ticker()
return rf
}
完成初始化后,开启一个线程来执行定时器的功能,即可定时触发选举以及心跳功能了。
定时触发即为我们上述介绍到的根据服务器的timer定时时间以及角色的设置,来执行不同的功能。2A部分主要执行选举这一功能。
当触发选举功能之后,将转变状态为candidate角色,并发的向其他服务器发送投票请求。
func (rf *Raft) leaderSelect() {
rf.currentTerm += 1
rf.votedFor = rf.me
voteCount := 1
lastIndex := len(rf.logBuffer) - 1
lastTerm := 0
if lastIndex == -1 {
lastTerm = 0
} else {
lastTerm = rf.logBuffer[lastIndex].term
}
voteRequestArgs := RequestVoteArgs{
Term: rf.currentTerm,
CandidateId: rf.me,
LastLogIndex: lastIndex,
LastLogTerm: lastTerm,
}
rf.resetElectionTimer()
for serveId, _ := range rf.peers {
if rf.me == serveId {
continue
}
go rf.voteProcess(serveId, &voteRequestArgs, &voteCount)
}
}
候选者收到其他服务器的回复之后,将根据其他服务器的回复进行相对应的执行,直到支持者的数量占据大部分时,将会变为leader,并完成一些初始化操作。
func (rf *Raft) voteProcess(server int, args *RequestVoteArgs, voteCount *int) {
reply := RequestVoteReply{}
ok := rf.sendRequestVote(server, args, &reply)
for !ok {
// 失败重传
if rf.killed() {
return
}
ok := rf.sendRequestVote(server, args, &reply)
if ok {
break
}
}
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.currentTerm < reply.Term {
rf.currentTerm = reply.Term
rf.role = ROLE_FOLLOWER
MyPrint(rf, "currentTerm < reply.term")
return
}
if rf.currentTerm > reply.Term {
MyPrint(rf, "currentTerm > reply.term")
return
}
MyPrint(rf, "reply.VoteGranted is %v", reply.VoteGranted)
if reply.VoteGranted {
*voteCount = *voteCount + 1
MyPrint(rf, "votecount is %v", *voteCount)
if *voteCount > len(rf.peers)/2 && rf.role == ROLE_CANDIDATE && rf.currentTerm == args.Term {
rf.role = ROLE_LEADER
*voteCount = 0
rf.nextIndex = make([]int, len(rf.peers))
for i, _ := range rf.nextIndex {
rf.nextIndex[i] = len(rf.logBuffer)
}
rf.timer.Reset(rf.heartbeat)
return
}
} else {
return
}
}
其他服务器收到来自候选者的投票请求时,也将根据上述图中流程介绍的那样,进行对应的执行。
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.killed() {
reply.VoteGranted = false
reply.Term = -1
MyPrint(rf, "server killed")
return
}
if rf.currentTerm > args.Term {
reply.VoteGranted = false
reply.Term = rf.currentTerm
MyPrint(rf, "currentTerm > args.Term, refuse to vote")
return
}
// 状态重置
if rf.currentTerm < args.Term {
rf.currentTerm = args.Term
rf.votedFor = -1
rf.role = ROLE_FOLLOWER
}
logJudge := true
logLen := len(rf.logBuffer)
if logLen > 0 {
if (rf.logBuffer[logLen-1].term > args.Term) ||
(rf.logBuffer[logLen-1].term == args.Term && logLen-1 > args.LastLogIndex) {
logJudge = false
}
}
if rf.votedFor == -1 {
if logJudge {
rf.votedFor = args.CandidateId
reply.VoteGranted = true
reply.Term = rf.currentTerm
rf.resetElectionTimer()
MyPrint(rf, "vote successfully")
return
} else {
reply.VoteGranted = false
reply.Term = rf.currentTerm
MyPrint(rf, "refuse to vote, logJudge is false")
return
}
} else {
// 由于网络延时等问题,再次收到选票
if rf.votedFor == args.CandidateId {
rf.role = ROLE_FOLLOWER
rf.resetElectionTimer()
MyPrint(rf, "refuse to vote, network is timeout, repeadly receive the request")
return
} else {
reply.VoteGranted = false
reply.Term = rf.currentTerm
MyPrint(rf, "refuse to vote, voted for another service %d", rf.votedFor)
return
}
}
}
以上便是2A实验部分的大体代码,除此之外,还需要完成GetState这个函数来供测试用例进行调用。
Finished~希望lab2B可以少点bug~