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. 为了效率,要一次性把能提交的一块提交(批处理)。
posted @ 2020-05-13 19:46  skyliulu  阅读(229)  评论(0编辑  收藏  举报