mit 6.824 Raft练习作业笔记

这是2021年国庆期望做的练习,随后整理完善了下。希望能对Raft感兴趣的同学一些启发。

做这个作业是为了熟悉Raft一致算法。基本的准备工作就不说了,可以对照着[https://pdos.csail.mit.edu/6.824/labs/lab-raft.html] 指导文档。

也是为了练习Go的编码。重复1000小时以上才能熟悉,1w小时以上才能精通。

0x01 2A部分

2A是适中的难度,也是第一个作业。实现Raft算法中的选Leader过程。

基础框架中已经给出出,需要在有2A注释的位置,加上实现的代码。保证最终的代码可以通过如下的测试用例。

go test -run 2A -race

一开始因为没有写什么代码,所以这个测试中3个用例全部失败。

先熟悉下用例的运行过程。

1.1 TestInitialElection2A

这个用例是用来初始化选举的,也就是说初始化完成后会出现一个Leader。

  1. 使用make_config生成一个集群配置。
  2. 调用配置的begin方法。这个时候应该已经开始选举了。
  3. 之后就是检查选举的状态了。

make_config时已经将各个Raft实例初始化,并连接到了一个虚拟的RPC网络上。现实中应该是一个个的进程,课程中是集成在了一个进程中,因为Go支持协程,很适合这种场景。

1.2 需要做哪些工作?

  1. 完成Raft类型的定义。
    除了论文中说的状态,还要保存当前的状态。状态有Candidate,Leader,Follower三个状态。
  2. 完成RequestVoteArgs和RequestVoteReply的定义,这个可以直接照搬论文里的。
  3. 实现RequestVote处理收到的投票请求。
    RequestVote中处理收到的选举请求。根据论文中的逻辑实现就行了。
  4. 发起投票前开始前要随机等待一段时间。在ticker中发起投票,向所有的peer发送投票RPC请求。
  5. 在Make中初始化raft实例。
    初始化时,需要设置初始的状态。term和votedFor属性,包含当前的状态。

1.3 理清选举的细节

  1. Leader如何维持自己的状态?
    Leader通过周期性的发心跳请求给各个peer,一旦有follower没有在指定的时间内收到心跳,就认为Leader异常了,就会在超时时间之后开始新一轮的选举。

  2. 如何保证已经投票给了A的peer,不会投票给B?
    这个要实现方案自己保证。并且如果已经投票了,但在指定的超时时间内不能转换为Follower状态,需要重新发起选举。

0x02 2B部分

事务提交的过程。

按照论文5.3中的描述,Leader收到客户端的请求后,leader会将请求entry附加到log列表之后,然后并发向所有其他的节点发送AppendEntries RPC请求。当这个entry被安全地复制了,leader将entry应用到自己的state machine上,并响应客户端。论文中提到了,如果没有并安全地复制,leader将一直会重复发送AppendEntries请求。即使是客户端已经被告知超时了,请求失败了。

通过梳理发现,要想搞清楚这部分编码要如何进行,就要理清楚这个操作中涉及到的几个关键动作和概念。

2.1 entry列表和状态机

figure2中,每个节点有3个持久化的属性。

  1. currentTerm,指代当前的term轮次。
  2. votedFor,当前term下被选出的节点。
  3. log[],这个是个状态机的entry列表。

其实这3个属性就决定了状态机的状态。2B中需要关注的是,状态机中跟业务直接相关的部分就是log[],当节点启动时,可以初始化出一个字典来。通过将log[]中所有的条目应用到字典上,就得到了启动后的字典值。这个字典可以提供给客户端进行查询。

现在可以说明commitIndex和lastApplied这两个运行时属性了,其实就是log[]中的索引。commitIndex就是达成共识的标引,lastApplied是最新应用到状态机中的索引。

2.2 收到AppendEntries时Follower如何处理?

按照论文中的流程,是要做一些判断才能应用log。必要时候是要丢弃掉不符合的日志。当一个leader的log还复制给多数节点时集群崩溃恢复了,旧leader在随后的时间里加入到集群中时,之前未复制的log就要丢弃掉,否则无法同步集群中新的日志。

2.3 初始化时如何向follower复制日志?

想理清leader的log是如何复制给follower的,就要从leader的初始化说起。

  1. 从持久化中加载Log列表,初始化commitIndex和lastApplied属性。
  2. 成为leader后,初始化nextIndex和matchIndex。

根据figure2中的描述,nextIndex将被初始化为log列表中最后一个条目的Index+1。而matchIndex被初始化为0。当触发心跳向所有主机发送AppendEntriesRPC请求时,假如有一个长时间离线的follower恢复在线,它的currentTerm小于当前的leader的currentTerm,将响应success为false,即告诉leader,它的数据和leader的不一致。然后leader会减小对应的nextIndex进行重试。

重试时,leader因为nextIndex与最后一个log的Index相等了,会把最后一个log条目附加到AppendEntriesRPC请求中,发给follower,如果还不成功。再次减小nextIndex,直到匹配上follower的最后一个log条目。至此follower就能同步到leader的数据了。

那么通过以上的分析可知:

  1. leader需要保留所有的log,不能清除历史的,防止有新的follower加入。 工程实现上可能不太好,所以有了快照传送的数据同步方式。
  2. follower与leader的差别较大时,可能需要很长时间才能同步成功。 可以用二分法快速找到差别点。

同样可以知道nextIndex和matchIndex属性的作用。
nextIndex: 保存下一个要向follower同步的Log索引,它规定了从哪个位置向follower拷贝Log。
matchIndex: 记录了已经成功复制的最大Index,通过它来确认某个Index是否已经达成多数复制了。

为什么需要两个Index列表描述呢?

是的,按照figure2中的说明,matchIndex和nextIndex是一起更新的。好像是可以只用一个来表示。不过通过重新回顾初始化过程,就能明白只有一个是不行的。nextIndex会在同步协商过程中变动,而matchIndex的更新是严格要求的,它用来指示follower实际的复制索引。

0x03 阶段总结

2B已经完成。代码在[https://gitee.com/linmaolin/mit6.824] 中。感兴趣的同学可以看看,一起交流学习。

快照传输部分还没有做。

学到了如下:

  1. Go中单元测试要开启-race选项,用来检查竞争读写。
  2. 实打实地熟悉了Raft协议的选举和日志复制过程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值