Raft Part C | MIT 6.824 Lab2C Persistence

1,808 阅读1分钟

书接上文Raft Part B | MIT 6.824 Lab2B Log Replication

实验准备

  1. 实验代码:git://g.csail.mit.edu/6.824-golabs-2021/src/raft
  2. 如何测试:go test -run 2C -race
  3. 相关论文:Raft Extended
  4. 实验指导:6.824 Lab 2: Raft (mit.edu)

实验目标

  1. 完成persist()readPersist()函数,编码方式参照注释。
  2. 优化nextIndex[]回退方式,否则无法通过所有测试。

一些提示

  1. 测试涉及服务器故障和RPC失败等不确定事件,多次运行测试确保通过。
  2. 需要持久化的部分包括currentTermvotedForlog
  3. 有关nextIndex[]回退优化可以查看Students' Guide to Raft
  4. 在Lab2A和Lab2B中测试未能发现的错误可能会在Lab2C中暴露出来。

持久化

这部分其实很简单,代码中的注释已经很清晰了,当然你要注意data race问题。

func (rf *Raft) persist() {
	w := new(bytes.Buffer)
	e := labgob.NewEncoder(w)
	e.Encode(rf.currentTerm)
	e.Encode(rf.votedFor)
	e.Encode(rf.log)
	rf.persister.SaveRaftState(w.Bytes())
}

func (rf *Raft) readPersist(data []byte) {
	if data == nil || len(data) < 1 {
		return
	}
	r := bytes.NewBuffer(data)
	d := labgob.NewDecoder(r)
	d.Decode(&rf.currentTerm)
        d.Decode(&rf.votedFor)
        d.Decode(&rf.log)
}

nextIndex优化

Part B中对于失败的AppendEntries请求,让nextIndex自减,这样效率是比较慢的。

优化点1

如果follower.log不存在prevLog,让Leader下一次从follower.log的末尾开始同步日志。

优化点2

如果是因为prevLog.Term不匹配,记follower.prevLog.TermconflictTerm

  1. 如果leader.log找不到Term为conflictTerm的日志,则下一次从follower.logconflictTerm的第一个log的位置开始同步日志。
  2. 如果leader.log找到了Term为conflictTerm的日志,则下一次从leader.logconflictTerm的最后一个log的下一个位置开始同步日志。

nextIndex的正确位置可能依旧需要多次RPC才能找到,改进的流程只是加快了找到正确nextIndex的速度。

AppendEntries中有逻辑如下。

reply.Term = rf.currentTerm
reply.Success = false

if len(rf.log) <= args.PrevLogIndex {
    reply.ConflictIndex = len(rf.log)
    reply.ConflictTerm = -1
    return
}

if rf.log[args.PrevLogIndex].Term != args.PrevLogTerm {
    reply.ConflictTerm = rf.log[args.PrevLogIndex].Term
    for i := 1; i <= args.PrevLogIndex; i++ {
            if rf.log[i].Term == reply.ConflictTerm {
                    reply.ConflictIndex = i
                    return
            }
    }
}

Heartbeat中有逻辑如下。

if !reply.Success {
    if reply.ConflictTerm == -1 {
        rf.nextIndex[id] = reply.ConflictIndex
    } else {
        conflictIndex := -1
        for i := args.PrevLogIndex; i > 0; i-- {
                if rf.log[i].Term == reply.ConflictTerm {
                        conflictIndex = i
                        break
                }
        }
        if conflictIndex != -1 {
                rf.nextIndex[id] = conflictIndex + 1
        } else {
                rf.nextIndex[id] = reply.ConflictIndex
        }
    }
}

实验总结

Part C并不算是Raft算法的核心部分,关于nextIndex的优化本文是参照了Students' Guide中的方式。

如果你完成了持久化和回退优化两个部分依然无法通过所有测试,那可能要仔细的检查Part A和Part B是否遗漏了某些细节。

image.png

最后,为了证明我不是在乱写,附上我的测试结果。