RAFT实现之Log Replication
RAFT实现之Log Replication
测试通过
实现要点
1.AppendEntriesRPC
接收者(follower,candidate)除了实现图中的1~5,还需要注意以下几点:
-
收到更大的term,或者candidate收到leader的心跳包,转为follower
//收到更高的term,更新term,转为follwer
if leaderTerm > rf.currentTerm {
rf.convertToFollwer(leaderTerm)
//候选人收到leader的心跳包,转为follwer
} else if leaderTerm == rf.currentTerm && rf.state == candidate {
rf.convertToFollwer(leaderTerm)
}
-
如果follower日志和leader的冲突,需要将冲突信息(conflictTerm、conflictIndex)返回给leader,这里是论文中的方法优化版,可以让leader迅速定位冲突的日志位置。
if prevLogIndex >= len(rf.log) {//如果prevLogIndex处的日志不存在
reply.Success = false
reply.ConflictIndex = len(rf.log)
reply.ConflictTerm = -1
return
//如果prevLogIndex的日志存在,但是term不一致
} else if rf.log[prevLogIndex].Term != prevLogTerm {
reply.Success = false
reply.ConflictTerm = rf.log[prevLogIndex].Term
flag := false
//返回冲突term的最开始的index
for i:=prevLogIndex-1;i>=0;i-- {
if rf.log[i].Term != reply.ConflictTerm {
reply.ConflictIndex = i+1
flag = true
break;
}
}
if !flag {
reply.ConflictIndex = 0
}
return
}
2.RPC回复处理
发送者(leader)收到rpc调用的回复后,需要注意以下几点:
-
收到更大的term或过期的term,做相应的处理
//收到的trem大于自己的term,转为follwer
if reply.Term > rf.currentTerm {
rf.convertToFollwer(reply.Term)
}
//如果收到过期的term,丢弃
if rf.state!=leader || reply.Term < rf.currentTerm {
return false
}
-
正确的更新nextIndex、matchIndex,根据rpc调用时的entries的长度来更新,而不是自己的log长度
rf.nextIndex[serverid] = args.PrevLogIndex+len(args.Entries)+1 rf.matchIndex[serverid] = args.PrevLogIndex+len(args.Entries)
-
要迅速定位和follower的冲突日志的index,以一个term为单位来检查是否有冲突,而不是一个个entry来找
//加速复制
firstConflict := reply.ConflictIndex
if reply.ConflictTerm != -1 {
flag := false
for i,entry := range rf.log {
if entry.Term == reply.ConflictTerm {
if !flag {
flag = true
}
firstConflict = i
} else if flag {
break
}
}
}
rf.nextIndex[serverid] = firstConflict
3.提交状态机
一个守护进程周期性的检查能否提交状态机
func (rf *Raft) PeriodicApplyMsg() {
for {
//检查commitIndex > lastApplied
//大于则增加lastApplied,并提交到状态机
rf.mu.Lock()
var applyLogs []entry
commitIndex := rf.commitIndex
lastApply := rf.lastApplied
if commitIndex > lastApply {
applyLogs = rf.log[lastApply+1:commitIndex+1]
rf.lastApplied = commitIndex
}
// leaderId := rf.leaderID
rf.mu.Unlock()
if len(applyLogs) > 0 {
for i,entry := range applyLogs {
rf.applyCh <- ApplyMsg{true,entry.Command,lastApply+1+i}
}
}
time.Sleep(10*time.Millisecond)
}
}
这里需要注意两点:
1. 提交的时候不要给applyCh加锁
2. 为了效率,要一次性把能提交的一块提交(批处理)。
浙公网安备 33010602011771号