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. 为了效率,要一次性把能提交的一块提交(批处理)。