[MIT6.824 lab2] Raft2B-日志同步 实现要点 & 踩坑记录

137 阅读2分钟

课程地址:nil.csail.mit.edu/6.824/2021/…
Raft中文论文:yuerblog.cc/wp-content/…
Raft动画:www.kailing.pub/raft/index.…

目标

在2A课程的基础上,服务器添加储log数据的要求,需要保证在各种故障环境下,保证log数据的强一致性。

主要任务

围绕log需求,需要以下几个点进行改造:

  1. 选举投票阶段,需要检查candidate的日志的完整度,只能投票给日志不落后给自己的candidate
  2. candidate当选为leader后,通过心跳让followers的log与自己保持完全一致(全量)
  3. 后续的心跳中,leader需要携带新的log数据,用于与followers进行log同步(增量)

设计要点

投票约束

Raft 通过⽐较两份⽇志中最后⼀条⽇志条⽬的索引值和任期号定义谁的⽇志⽐较新。如果两份 ⽇志最后的条⽬的任期号不同,那么任期号⼤的⽇志更加新。如果两份⽇志最后的条⽬任期号 相同,那么⽇志⽐较⻓的那个就更加新。

  • 如果候选人的最后一条日志的term < 投票人最后一条日志的term,拒绝投票
  • 如果候选人的最后一条日志的term == 投票人的最后一条日志的term,但是该日志的索引小于,拒绝投票

log 全量同步

当leader成功当选后,要做的第一件事就是同步自己的log到所有follower中,follower必须无条件丢弃与leader不一致的log,然后保存leader的log数据。 follower首先要做的找到与leader一致的地方,确定一致位置的方式是根据心跳rpc中的preLogIndex和preLogTerm与自己对应位置的log进行判断。

  • 如果本地preLogIndex位置的log的term == preLogTerm,表示前面位置的所有日志已经一致,follower丢弃从preLogIndex之后所有log,然后将leader的日志append。
  • 如果上面判断出term不一致,则rpc reply中result 设为false,告诉leader同步失败,这样leader下次心跳rpc中索引位置就会是的preLogIndex-1. 按照这个方式不断向前找到一致点。

commitlog 和 appliedIdx

  • commitlog 指所有对log达到一致的位置
  • appliedIdx 表示当前服务已经将log数据写入实际数据的位置

应用日志到状态机

如果commitIndex > lastApplied, 那么lastApplied++,并把log[lastApplied]应用到状态机中

当超过半数的节点有存储一个index位置的log时,leader就可以对该log进行提交。 这里的设计比较直接,在log结构体中使用一个字段表示已经同步的节点数量.

type LogEntry struct {
   // 日志索引位置
   Index int
   // 收到该日志时的term
   Term int32
   // 数据实体
   Command interface{}
   // 已经进行同步的节点数量
   Copies int
}

当leader收到一个follower的心跳reply时,如果result = true时,将args.preLogIndex+1位置到最后一个位置中间所有log.copies+1,然后判断最后一个log是否超过半数即可。

注意点

测试用例中,log的下标使用1开始的,所以在下面两个地方有进行index+1的操作,当时因为这个点导致测试用例一直不通过,排查了很久。

func (rf *Raft) Start(command interface{}) (int, int, bool) {
	index := -1
	term := -1
	isLeader := true

	// Your Code here (2B).
	rf.mu.Lock()
	defer rf.mu.Unlock()
	isLeader = rf.isLeader()
	if isLeader {
		index, term = len(rf.Log)+1, int(rf.Term)
		rf.Log = append(rf.Log, LogEntry{Index: index, Term: rf.Term, Command: command, Copies: 1})
	}
	return index, term, isLeader
}
func (rf *Raft) applyMsg() {
	if rf.CommittedIndex > rf.LastAppliedIndex {
		for idx := rf.LastAppliedIndex + 1; idx <= rf.CommittedIndex; idx++ {
			msg := ApplyMsg{Command: rf.Log[idx].Command, CommandValid: true, CommandIndex: idx + 1}
			rf.Apply <- msg
			rf.LastAppliedIndex++
		}
	}
}