在 lab2 实在是拖的太久了,今天终于通过了所有测试用例,先附上一张测试全部通过的截图。

image

说实话,做这个lab还是蛮吃力的,之前对并发编程接触的不多,不管是在写代码,还是在debug的过程中都遇到了很多的问题,通过全部测试后也是小有成就感。

因为这个lab要求不要公开代码,所有就不贴代码了。就写一些对raft的理解和完成这个lab遇到的一些问题。

关于 Raft

raft是一个共识算法,是简化版的paxos, 比paxos更好理解。raft解决的是问题就是保证多个副本中日志保持一致。

raft可以分为三个部分

  • leader election
  • log replication
  • safty

Leader Election

raft 节点的角色有三个:leader, follower, candidate。节点的初始状态是follower, 每个节点都有一个随机的超时时间,当follower节点超时时,其状态会变成candidate, candidate会向集群中的其他节点发起投票,开始选举,如果拿到多数选票,其状态就变成leader, 这就完成了leader election

Log Replication

raft的特点是只有leader做出决策,其他节点只要根据leader的指令来做就好,所以只有leader会接受来自客户端的日志,leader将接受到的日志发送给follower节点,如果集群中的大多节点都收到了这条日志,日志就可以commit, 然后apply

Safty

raft如何保证每个节点的日志的顺序一致?

  • leader必须存储所有已经提交的日志条目
  • 有最新日志的节点才能成为lader
  • 只提交任期内的日志条目

实现

image

参照raft 论文中的这张图和 lab2 代码的基本骨架实现。用一个goroutine控制节点状态的变更,利用channel进行goroutine间的通信。主要就是实现RequestVote RPCAppenEntries RPC

2A

2A 部分就是完成leader election, 完成RequestVote RPC, 以及不带日志的心跳。这个是之后的基础,一定要多运行几遍,这里如果有bug的话后面都没法通过。

遇到的坑

  • heartbeat channel要设置为有缓冲的,不然会因为 channel 阻塞导致election timeout
  • 计算选票是要判断节点状态,不然会导致不应该成为leadercandidate成为leader
  • 要通过race检查的话,读和写raft结构体中的数据都要加锁

2B

2B 部分是日志备份的部分,完成AppendEntries RPC, 完成 2A 后台按照论文中的图实现也不难,基本就是按照 2A 流程走,注意论文图中给的要求写就行。

遇到的坑

  • 论文中写的日志索引的初始值是 1,但是lab中的测试是由 0 开始的
  • follower节点需要返回给 leader 要下一次日志复制开始的索引,这个论文图中的ApendEntriesReply是没有的,要自己定义

2C

2C 部分是为了保证节点重启数据不丢失,需要将日志,节点状态,任期等信息持久化到磁盘。这个和lab1 mapreduce中的持久化很类似,我们只要完成对要持久化数据的编解码,然后在节点状态变更的地方调用persist函数即可。

总结

总体来说,这个lab还是有难度的,特别是刚开始做的时候,debug的时候很头疼, 当 2A 部分的测试都通过,race检测也通过,完成了 2A 部分,后面就简单多了,按照论文说明做的话,基本都会通过测试。完成这个lab也收获了很多,包括对raft的理解,对并发程序的debug, 对goroutinechannel的更深入了解,对锁的使用。.. 真的是非常感谢 MIT 能开源这样有意义的lab