前言

2333我终于考完试了,背垃圾课恶心到反胃的期末终于结束了,这里继续记录总结下,首先回顾Lab2PartA部分(半个月前就写完了)。

正文

首先是LabPartA只完成选主的任务,没有涉及到log的部分,所以在添加参数的时候就不需要关注那些log的部分。

首先我们需要明确,在raft里面每一个节点可能有三个身份leader,follower,candidate,这三者在某些条件下进行互相转换所以我们可以把其看做状态机,然后存在两个计时器,一个计时器heartbeatTimer用来leader发送心跳包,另一个electionTimer用作选举超时,然后利用一个goroutine来维护timer相关的事件,然后在PartA中我们只有两个动作,一个是leader广播心跳包,另一个是candidate发起选举,除了这两个动作,我们还需要几个辅助函数来:一个是状态转换函数convertTo(),还有 一个生成随机时机的函数randTimeDuration(),当然在此之前我们首先要做的是完善raft节点的结构体struct和两个rpc:RequestVote和AppendEntries。

先准备些预定参数结构和函数

在实验中我们的时间要根据lab的要求不能只看论文里面的,然后string函数用来格式化输出,定义Follower,Leader,Candidate三个状态。

这个函数用来得到一个随机事件在指定的区间内。

现在我们来正式完善一下整个代码,注意何时需要去加锁,何时需要重置选举计时器。我们来完善基本的raft的节点结构体。

在2A中我们只需要以上几个参数,这里我使用golang的Timer作为计时器。接下来完成下GetState()函数,这个函数是节点用来返回当前任期和身份

加下来完成RequestVoteArgs结构体和RequestVoteReply结构体,这两个结构体都是用于请求投票rpc中,然后我们还需要完成RequestVote()函数,这个函数是来完成请求投票这个逻辑动作,最后sendRequestVote()函数是真正进行发送rpc到整个raft网络中(mit应该是对net包进行包装成一个网络环境,来进行在单机上的环境模拟,牛逼还是mit牛逼)。

现在我们来分析下RequestVote()这个函数:
因为我们在这里会对rf中的数据进行修改,所以我们需要给其加锁

当请求投票的term小于当前节点的term,或者当两者term相等时,但此时节点已经投票给了其他节点(比如有另一个候选者发起了投票先得到了rf的投票)

否则rf就投票给候选者,更新term为候选者的term,VoteGranted成功,重置选举时间,然后rf转换为follower(不管是不是candidate还是本来就是follower)。

然后模仿着请求投票的rpc来写一下添加日志rpc(发送心跳包也是用这个rpc,只不过是发空包)。

再来看下添加日志这里逻辑怎么写

因为还是要对rf结构体中的值进行修改所以还是需要加锁,然后注意请求的term小于当前rf的term的话需要拒绝掉。否则就更新rf的term,当rf收到成功添加日志后需要重置选举计时器,然后转换为follower。

然后我们看Make()函数,这个函数负责处理维护raft结构体的状态逻辑。

任期初始化为0,默认没有投票,创建两个计时器,初始化状态为Follower。

然后维护一个goroutine来作为状态机更新状态,注意何时加解锁,当heartbeatTimer超时时发送一个信号,然后如果此时它是Leader就需要广播心跳,然后充值它的心跳计时器,在此期间都需要加解锁。

当选举计时器超时后,当rf为跟随者,那么他应该转换为候选者,否则它此时已经是一个候选者,本次选举没有成功选出,超时,那么就需要重新开始一次选举startElection()。

然后这个需要一个状态转换函数convertTo(),如果是转换的状态与自己一样那么直接返回就可以了,如果转换为Follower的话,停止heartbeatTimer计时器,重置投票对象;如果转换为Candidate的话,开始一次选举;如果转换为Leader的话,就停止electionTimer计时器,立刻广播心跳,然后充值心跳计时器。

现在我们来关注下最为重要的两个动作startElection(),broadcastHeartbeat():

首先看发起选举这个动作startElection():

根据论文描述,当candidate发起选举时,首先增加任期,然后给自己投票,这个声明一个变量voteCount用来记录是否收到超过半数的投票,进行原子操作。然后无论何时开启一次选举都需要重置当前节点的选举计时器。之后给节点列表peers发送投票请求,这个用协程来加速。

来具体看一下这个goroutine匿名函数,reply用来接收rpc返回值,调用sendRequestVote函数发送rpc请求给其他节点,在处理reply时需要给rf进行加锁,当VoteGranted参数为真并且此时rf还是候选者的身份时(因为有可能其他节点已经获胜,或者当前节点已经赢得了选举,已经不是candidate的身份),那么原子加一,并且判断时候获得的选票是否已经超过半数,此时Candidate转换为Leader。

否则VoteGranted为假,存在两种情况,一个是目标已经投票给了其他人,另一个是candidate的term比较小,所以当reply的term大于Candidate的term时候那么candidate更新term并且转换为Follower。

我们再看另一个动作broadcastHeartbeat(),在PartA中因为没有涉及到日志所以比较简单。

同样先准备参数,然后依次给其他节点发送心跳包(即空包),这里逻辑比较简单就不再赘述了。

看test结果:

总结

这个Lab1还是需要有一个比较清楚的逻辑,这里选用事件循环的逻辑,用状态机的思想对节点进行处理,最重要的还是要明确什么时候需要加锁,好了明天继续把PartB的总结写了。