总体思路
lab2A主要实现raft中leader选举问题,server中的角色有三种,leader、candidate,follower,它们各自职责如下:
- follower
a. 对candidate和leader的rpc进行回复
b. 如果超市时间内没有收到AppendEntries rpc 或者 收到candidate的投票,会讲自己的 状态转为candidate
2.candidate
a. 增加当前peer的term
b. 为自己投票
c. 重置选举超市时间
d. 发送RequestVote RPC 发送给其他peer
3.leader
如果发送的rpc收到的回复大多数都认可自己,那就变成leader
如果收到了AppendEntries RPC, 那就变成follower,说明有其他人在选举
如果超时时间过期了,那就开启一个新的term
2A主要包含处理来自leader和candidate的rpc请求AppendEntries、RequestVote;leader发送log同步和heartbeat请求;candidate发送选举请求
执行流程
首先完成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 *Persister // Object to hold this peer's persisted state
me int // this peer's index into peers[]
dead int32 // set by Kill()
// Your data here (2A, 2B, 2C).
// Look at the paper's Figure 2 for a description of what
// state a Raft server must maintain.
state int32 //0:follwer 1:candidate 2:leader
currentTerm int // 当前最新任期号
voteFor int //收到的投票的候选人id,没投就为空
log []Log //log条目
commitIndex int //已提交的最大的log条目索引
lastApplied int //应用于状态机的最大索引条目
nextIndex int //leader要发给server的下一个条目索引
matchIndex int //该服务器上最大的条目索引
electionTimeout time.Duration //选举超时时间,选举时随机初始化
getVotes int //候选人获得票数
startTime time.Time //开始时间
voteFinish chan bool //投票已完成
rpcRequest chan bool //收到rpc请求
isSendVoteFinished bool //是否发送了选举完成channel
}
func Make(peers []*labrpc.ClientEnd, me int,
persister *Persister, applyCh chan ApplyMsg) *Raft {
rf := &Raft{}
rf.peers = peers
rf.persister = persister
rf.me = me
// Your initialization code here (2A, 2B, 2C).
// initialize from state persisted before a crash
rf.readPersist(persister.ReadRaftState())
rf.state = 0
rf.currentTerm = 0
rf.voteFor = -1
rf.rpcRequest = make(chan bool, 10)
rf.voteFinish = make(chan bool, 10)
fmt.Println(len(peers), "peers")
go rf.ExeTask()
fmt.Println(me, "begin work")
return rf
}
然后是RequestVote
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
// Your code here (2A, 2B).
//对raft加锁
rf.mu.Lock()
fmt.Println(rf.me, "receive vote request from", args.CandidateId, "at", rf.startTime)
if args.Term > rf.currentTerm {
rf.voteFor = -1 //有更新的term初始化为未投
}
if args.Term < rf.currentTerm {
//任期比我小不给投
reply.VoteGranted = false
reply.Term = rf.currentTerm
return
} else {
if args.Term > rf.currentTerm {
rf.state = 0
rf.currentTerm = args.Term
}
if rf.voteFor == -1 || rf.voteFor == args.CandidateId {
//条件满足可以投票
reply.VoteGranted = true
reply.Term = rf.currentTerm
rf.state = 0
rf.voteFor = args.CandidateId
rf.rpcRequest <- true
fmt.Println(rf.me, "vote for ", args.CandidateId, "term is", args.Term, "at", time.Now())
}
}
rf.mu.Unlock()
}
RequestVote处理逻辑如下:
1.如果请求的周期大于本身的周期,不管什么状态,本身角色转为follow,并且周期变成更大的
2.如果请求周期小于自身周期,则直接返回
3.rf.votedFor == -1 || rf.votedFor == args.CandidateId raft论文中要求的写法,-1表示当前任期还未投票
4.最后投票成功后,重新设置自身选举时间,然后投票成功
AppendEntries实现思路
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
if rf.currentTerm <= args.Term {
rf.rpcRequest <- true
}
fmt.Println(rf.me, "receive heartbeat from", args.LeaderId, "at", rf.startTime)
reply.Term = rf.currentTerm
if args.Term < rf.currentTerm {
reply.Success = false
} else {
//检查条目是否匹配
if rf.state != 0 {
rf.state = 0 //有更新的term,自己要成为follwer
}
rf.currentTerm = args.Term
if len(rf.log) <= args.PrevLogIndex {
reply.Success = false
} else {
if rf.log[args.PrevLogIndex].Term == args.PrevLogTerm {
reply.Success = true
rf.currentTerm = args.Term
} else {
//如果索引一样,term不一样删除当前索引之后的条目
rf.log = rf.log[:args.PrevLogIndex]
reply.Success = false
}
}
}
rf.mu.Unlock()
}
heartbeat处理逻辑:
1.如果参数周期大于自身,则转follow
2.若小于,则带着更大的周期返回
3.心跳发送成功,更新领导人
接下来就是当follower转变为candidate发送选举请求
部分代码实现(全放有点长):
//先给自己投票并且将任期+1
rf.mu.Lock()
rf.currentTerm++
rf.voteFor = rf.me
rf.mu.Unlock()
//接下来初始化rpc参数等信息代码略...
//votges用来统计票数
for i, _ := range rf.peers {
//并行的向每个server发送请求
if i != me {
go rf.sendRequestVote(i, &args, &votes)
}
}
select {
case <-rf.voteFinish://完成投票
case <-time.After(time.Duration(rand.Intn(500+1)+500) * time.Millisecond)://选举超时
rf.mu.Lock()
rf.state = 1
rf.mu.Unlock()
}
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, votes *int) {
reply := RequestVoteReply{}
ok := rf.peers[server].Call("Raft.RequestVote", args, &reply)
if rf.isSendVoteFinished {
return
}
if ok {
if reply.VoteGranted {
*votes++
if (*votes) > len(rf.peers)/2 {
rf.mu.Lock()
rf.state = 2
rf.isSendVoteFinished = true
rf.mu.Unlock()
rf.voteFinish <- true
fmt.Println(rf.me, "is leader term is", rf.currentTerm, "at", time.Now())
//成为leader立马发送heartbeat
go rf.SendHeartbeat()
}
} else if reply.Term > rf.currentTerm {
rf.mu.Lock()
rf.state = 0
rf.currentTerm = reply.Term
rf.isSendVoteFinished = true
rf.mu.Unlock()
rf.voteFinish <- true
}
}
return
}
在投票选举的过程中如果出现了票数过半的情况就直接改变状态声明选举完成,跳到leader代码发送heartbeat
最后是发送heartbeat逻辑:
func (rf *Raft) SendHeartbeat() bool {
args := AppendEntriesArgs{}
args.LeaderId = rf.me
rf.mu.Lock()
args.Term = rf.currentTerm
myterm := rf.currentTerm
me := rf.me
rf.mu.Unlock()
replyNum := 0
reply := AppendEntriesReply{}
fmt.Println(rf.me, "send heartbeat request at", time.Now())
for i, _ := range rf.peers {
if i != me {
if rf.sendAppendEntries(i, &args, &reply) {
replyNum++
if reply.Term > myterm {
rf.mu.Lock()
rf.state = 0
rf.currentTerm = reply.Term
rf.mu.Unlock()
return false
}
} else {
//fmt.Println(me, "can not send heartbeat to", i)
}
}
}
if replyNum < len(rf.peers)/2 {
rf.mu.Lock()
rf.state = 0
rf.mu.Unlock()
}
return true
}
发送心跳时当返回有更大的term则直接变为follower
注意细节
以上为2A的主要内容,因为刚开始学习分布式,go语言也不熟悉,所以刚开始走了很多弯路,代码中有不对的地方请各位批评指正。经过测试改代码大部分情况下都能通过测试(不能保证每次都能通过测试)主要出现以下问题:
多个候选人同时发起选举导致term一直增加,无法得到leader
第二种情况是当得到leader后立马又有新的候选人出现成为新的leader
这种情况是当某个follower给某个candidate投票后,candidate成为了leader,但此时follower正好超时了所以follower会变为candidate发起选举请求成为新的leader
在实现过程中超时管理的实现是一个比较头疼的问题(并发程序的代码真不好调啊…)
开始的时候我是直接采用设置一个startTime time.Time变量然后每次有rpc请求时就跟新startTime 然后time.Sleep()一段时间最后检查当前时间点与startTime的差有没有超出设置的时间,结果测试的时候当有网络崩溃的时候要不就没法选出leader要不就有多leader产生,所以后面再实现的时候采用select+time.After()就好多了,不得不说go这方面还是好用的。
select {
case <-rf.rpcRequest:
case <-time.After(time.Duration(rand.Intn(200+1)+200) * time.Millisecond):
rf.mu.Lock()
rf.state = 1
fmt.Println(rf.me, "become candidate at", time.Now())
rf.mu.Unlock()
}
还有一点要注意的是论文中提到的超时时间大小的设置问题(这可能是导致无法选出leader的原因),另外在发送选举请求时当有超过半数票时就立马宣布选举完成并发送heartbeat不然可能会有新的follower超时。