MIT 6.5840(6.824) | Lab2: Raft

本篇文章主要讲述学习raft的road map与我自己踩的一些坑。

网络上关于raft的讲解实在是太多了,我作为一名初学者很难做到比各位大佬讲解的更好,因此打算对现有的资料进行一些整合,帮助初学者更快的上手raft。

开始之前

在开始之前,你至少应该略读过raft论文,对figure 2有基本的认识,了解文中每个section具体在解决什么问题,以防之后debug时无从下手。

figure 2

同时,你应该看过6.824的raft课堂回放,Robert Morris 教授的讲解十分清晰,这里推荐肖宏辉大佬整理的图文翻译版课程内容方便随时回看。

现在,你应该对raft有大概的认识了,如果还没有的话,推荐这个可视化raft的网站,可以帮助你快速的建立起对raft的认知。助教提供的diagram也可以帮助你理解raft的内部机制。

raft diagram

开始编码

尽管lab页面提供了大量关于结构设计与锁的相关instructions,但是作为初学者在没有真正熟悉Go与raft的机制之前很难理解其中的具体内容。这里我推荐先参考助教的debug博客将log工具配置好。相信我,你一定会用到它的(。

Leader election

经过以上内容的学习,你应该可以做到在脑海里可视化raft的机制并开启lab 2A的编码工作了。Leader选举并不涉及到log的添加,这里所有有关于log的逻辑都可以暂不实现,包括但不限于含nextIndex, matchIndex, logIndex, logTerm的逻辑。

一开始我想通过一个类似于如下的函数实现有限自动机,进行统一的follower, candidate与leader状态转换,但实际上很难实现,这样的方式很难实现并发编程与锁的获取与释放,遂而放弃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (rf *Raft) EmitStateTransfer(event RfEvent) {
switch event {
case ReceiveHeartbeat:
if rf.rfState == CANDIDATE {
rf.transferCandidateToFollower()
} else if rf.rfState == FOLLOWER {
rf.resetHeartbeatTimer()
}
case HeratbeatTimeout:
if rf.rfState == FOLLOWER {
rf.transferFollowerToCandidate()
}
case ElectionTimeout:
if rf.rfState == CANDIDATE {
rf.resetElectionTimer()
}
case ReceiveMajorityVote:
if rf.rfState == CANDIDATE {
rf.transferCandidateToLeader()
}
case CurrTermLower:
if rf.rfState == CANDIDATE {
rf.transferCandidateToFollower()
} else if rf.rfState == LEADER {
rf.transferLeaderToFollower()
}
}
}

同时,我这里还踩的一个大坑就是没有分清楚electionTimeOut与heartbeatTimeOut。heartbeatTimeOut只对当前的leader作用,发生heartbeat超时就发送一轮空entries来保证follower的electionTimer不会超时。

关于锁,如果你不确定的话,一切读取与修改raft节点状态的代码加上一把锁即可保证平安,这时再读lab提供的关于锁的instructions会更加清楚。

如果你能仔细的根据figure 2来编写代码的话,相信leader election难不倒你。在开始lab2B之前,建议使用-race参数进行多次测试保证leader选举时没有数据竞争的发生。

Log replication

日志复制是raft细节最多的地方,不注意就会出现bug,我在这里花费了大量时间进行调试。

这里我踩的第一个坑是log的初始index,我没有注意到figure 2中说明了log的初始index为1,花费了大量时间研究测试代码和调试才发现问题。

另一个大坑是appendEntries应该与reply的处理在同一个goroutine中进行,否则会因为有大量的rpc触发导致处理reply的进程抢不到锁,进而无法更新nextIndex与commitIndex,raft无法顺利推进下去。

同时你也可以参考SOFAJRaft 日志复制为每一个raft节点都开一个replicator goroutine来批量异步的进行日志复制,这可以大大减少rpc的调用与重复信息的发送。

Persistence

待更新

Log compaction

待更新

测试时间优化

你可以通过尝试调整electionTimeOut与heartbeatTimeOut的时间来加快测试进度。同时也可以使用助教提供的可视化测试脚本同时运行大量测试用例。

遇到瓶颈?

推荐参考OneSizeFitsQuorum大佬的lab文档,相信你一定能受到启发。


MIT 6.5840(6.824) | Lab2: Raft
http://bustdot.github.io/2023/08/22/MIT-6-5840-6-824-Lab2-Raft/
作者
BustDot
发布于
2023年8月22日
许可协议