Open moyuanhuang opened 6 years ago
Figure.2 rules for server: "for All servers: if RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower." Consider this scenario:
RequestVote RPC
,注意此时RPC中的Term已变为2。votedFor
,把Term2的票投给F1。综上,本来只是一个Follower(F2)自己的网络出了问题,最后却导致Leader改变,Term更替。这样岂不是很划不来??Term1的Leader好冤啊。而且如果Follower网络问题频繁的话,不是总得换leader?
func (rf *raft) Start(command interface{}) int, int, bool
。其中每个返回值用来保存什么信息在源代码的注释中已经做出解释。除了接收Log,最重要的就是每个Peer可以通过Leader对日志达成共识,以达到最后Log committed的目的。config
向Raft输入command
的两种方法sync.Cond
条件变量需要在哪里使用AppendEntries RPC
rf.logs
rf.commitIndex
[x] 2A: Implement leader election and heartbeats 这部分的试验主要实现3个
raft.go
中的函数,当然还需要看懂理解其他几个类的作用。另外,根据这门课助教的提醒,一定要严格遵守论文中Figure.2的实现细节。(我真的就是踩了其中的几个坑导致写了整整两天啊摔)config.go
除了raft.go
之外最重要的文件,相当于是client(tester)与Raft的接口。其中config.net
没有使用golang中自带的rpc package,而是自己实现了一个labrpc包,为了增加模拟network failure的功能以便测试Raft。endnames
是储存了Raft server之间的“代号”,每次server i
重启的时候都会刷新相应的endnames[i]
数组。这个地方不明白没关系,可以暂时跳过。 这里有一个困扰我很久的问题就是clientEnd
变量的角色。一开始我以为是向Raft发送Entry的app client,导致代码很多地方都讲不通。后来才明白这里的Client是针对Raft内部server来讲的,也就是每个client代表的都是一个Raft server
,为了避免混淆,后面都把这些内部的server叫做peers
。persist.go
用来持久化每个server的状态,包括内部状态以及数据,在之后的试验中还会用到这里的snapshot函数。util.go
这里提供了一个debug函数,可以将Debug设为1来打印程序中所有用DPrintf打印的输出。之后将debug设置成0就可以直接看见测试结果啦(如果用fmt.Printf
就要一个一个删掉),强迫症患者福音。votedFor int
,这个变量记录了当前term的leader票投给了谁,我将它初始化为-1代表本轮还未投票。一旦voteFor
被赋上了非负值,说明这个peer
已经投过票。在paper中有这么一句不起眼的话因此,在term改变之前,只要
voteFor
非负,peer会拒绝其他所有的RequestVote RPC
。除此之外,我还加上了constructor(
Make()
) 初始化一个新的raft peer。在构造器中除了对每个成员进行初始化之外,更重要的一件事就是启动一个新的goroutine来控制自己的state
。对于在不同状态下的peer有不同的转换逻辑,在Figure.2的Rules for servers中给出。这里的难点在于如何在收到heartbeat后reset electionTimeOut。根据lab指导,有同学会使用time.Sleep(electionTimeOut)
。我认为这样是不准确的:相当于只要Sleep的时间内收到一次heartbeat,goroutine醒来后就会重置electionTimeOut
,实际上是延长了electionTimeOut
时间。我的方法在这里,大概就是每隔time.Sleep(BroadcastInv)
来检查heartbeat。RequestVote RPC
AppendEntries RPC