MIT 6.284 Lab2A Raft
一、前期准备
实验地址introduction部分有一些资料可以学习一下
课堂翻译强烈推荐(里面还有相应论文),虽然可能没有上课听老师讲那么好,但是对于英语渣渣的我还是非常有用的。
代码主要分为测试部分和编写部分,可以先去大致看一下config和test文件中的内容,毕竟程序准确性依赖测试文件,同时也能知道实验各个部分的运行过程。
论文中已经把算法基本上都已经实现好了,先把需求和代码看清楚再下手(课上老师好像也这么说了)
二、实验要求
该实验主要是让我们进行leader的选举,测试文件有三个,一个是初始化后的选举,一个是当网络出现异常时选举,以及多次迭代,随机断开网络连接选举。
根据实验要求,我们需要实现的函数功能有leader选举,leader定时发送心跳,以及角色转换。
三、思考
因为在之前还没接触过go,C++用的比较多,所以先想了一下如果用C++实现该如何(多机),因为之前看过muduo网络编程,将行为都定义为事件,之后统一epoll触发,调用回调函数。
但go中有channel和协程导致结构可以很简单,根据上图我们也可以根据状态自动机的思想来简化代码结构。
for rf.killed() == false {
switch rf.state {
case FLLOWER:
select {
case <-心跳:
case <-投票:
case <-超时:
}
case LEADER:
广播心跳
time.Sleep(HBINTERVAL)
case CANDIDATE:
选举前处理
广播给其他节点 使其为自己投票
select {
case <-选举超时:
case <-收到心跳:
case <-选举成功:
}
}
}
根据选举的行为和上图的角色转换,该实验的整体代码就够差不多就这样。
选举原则:
- 每个节点,对每个任期(term)只能投出一张票.
- 收到选举请求后,如果该请求的事务日志编号小于自己的机器,那么就不会投票.
- 保证新的leader拥有更新的数据 收到选举请求后,如果该选举编号(term)小于自己的term,那么就不会投票.
- 当一台机器收到多数投票,代表选举成功成为新的leader.
四、数据结构定义
数据结构在论文中都有定义,可以直接照抄,同时论文中还提到可以将心跳和提交信息一起发。(当然当前实验还用不到,在实验2B中需要)
type Raft struct {
mu sync.Mutex // Lock to protect shared access to this peer's state
peers []*labrpc.ClientEnd // RPC end points of all peers
persister *Persister // Object to hold this peer's persisted state
me int // this peer's index into peers[]
dead int32 // set by Kill()
// Your data here.
// Look at the paper's Figure 2 for a description of what
// state a Raft server must maintain.
//channel
state int
voteCount int
chanCommit chan bool
chanHeartbeat chan bool
chanGrantVote chan bool
chanLeader chan bool
chanApply chan ApplyMsg
//persistent state on all server
currentTerm int
votedFor int
log []LogEntry
//volatile state on all servers
commitIndex int
lastApplied int
//对于每一个服务器,记录需要发给它的下一个日志条目的索引(初始化为领导人上一条日志条目的索引值 + 1)能够起到同步作用(例如跟随者故障之后 恢复)
nextIndex []int
//对于每一个服务器,记录已经复制到该服务器的日志的最高索引值(从0递增)
matchIndex []int
}
type RequestVoteArgs struct {
//任期
Term int
//全局唯一候选者ID
CandidateId int
//候选人最新日志条目对应的任期号
LastLogTerm int
//候选人最新日志条目的索引值
LastLogIndex int
}
type RequestVoteReply struct {
//表示确认的server的任期 如果candidate的term小于它。则更新
Term int
//表明该follower是否给自己投票
VoteGranted bool
}
type AppendEntriesArgs struct {
//该term为leader的当前任期号
Term int
//全局leader唯一id
LeaderId int
//最新日志之前的日志的leader任期号
PrevLogTerm int
//最新的日志之前的索引值
PrevLogIndex int
//将要存储的日志条目
Entries []LogEntry
//leader提交的日志条目索引值
LeaderCommit int
}
type AppendEntriesReply struct {
//收到所有server节点的rpc回复term
Term int
//是否认可当前leader身份 继续为主节点
Success bool
NextIndex int
}
五、具体函数伪代码
5.1 选举
选举时无需所有follower都返回结果,只要有超过半数投出了赞同票即可。
func (rf *Raft) sendRequestVote(server int, args RequestVoteArgs, reply *RequestVoteReply) bool {
ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
rf.mu.Lock()
defer rf.mu.Unlock()
if ok {
rf状态变化。。
follower投自己一票。。
}
return ok
}
func (rf *Raft) RequestVote(args RequestVoteArgs, reply *RequestVoteReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
defer rf.persist()
处理term不满足要求情况
只有候选人满足投票条件才能投票(任期比自己高或者等于自己,同时拥有最新的日志)
}
func (rf *Raft) broadcastRequestVote() {
var args RequestVoteArgs
填充args数据
for i := range rf.peers {
if i != rf.me && rf.state == CANDIDATE {
//并行的推送给所有节点进行选举leader
go func(i int) {
var reply RequestVoteReply
rf.sendRequestVote(i, args, &reply)
}(i)
}
}
}
5.2 心跳
在该实验中可以将发送心跳和日志放在一起写所以该部分放到下一篇。
type LogEntry struct {
//日志索引
LogIndex int
//该命令对应的领导人任期号
LogTerm int
//可以执行的命令
LogCommand interface{}
}
func (rf *Raft) broadcastAppendEntries() // 和上一个差不多
func (rf *Raft) sendAppendEntries(server int, args AppendEntriesArgs, reply *AppendEntriesReply) bool {
ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
rf.mu.Lock()
defer rf.mu.Unlock()
if ok {
if rf.state != LEADER {
return ok
}
if args.Term != rf.currentTerm {
return ok
}
2B的内容
}
return ok
}
func (rf *Raft) AppendEntries(args AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
defer rf.persist()
// 具体留到2B再写
}
六、算法缺陷
6.1 缺陷1:假设有一个Leader l1,和两个follower f1,f2,当f2发生网络故障时,会因为收不到l1的心跳,导致f2的term一直增加,当f2网络恢复正常时,因为他的term是最大的,会触发一次选举,更新其他节点的term,但由于f2的日志不是最新的,所以也不会选举成功。
解决方法:PreVote,Candidate首先要确认自己能赢得集群中大多数节点的投票,这样才会把自己的term增加,然后发起真正的投票。
赢得投票条件为:
- 没有收到有效领导的心跳,至少有一次选举超时。
- Candidate的日志足够新