引言
恰逢寒假准备实习,便重新拾起6.824的lab,lab2主要是对Raft论文的复现,本文实现了2A部分的内容,lab2的内容难度还是很大的,写下此文便于日后复习使用。
Reference:
正文
2A部分将实现领导人选举和心跳包的发送,关于Raft工作原理的内容不再赘述,详见Raft论文。
一、结构体的创建
通过论文中的figure 2,便可知晓整个Raft算法的大致框架以及逻辑过程。通过以下几张图分别创建不同的结构体。
此图反映了所有server共同存在的属性,将其填入事先给出的Raft结构体中,此外,还添加了图中未提到的一些属性,如超时时间,rf的状态等
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 (2A, 2B, 2C).
// Look at the paper's Figure 2 for a description of what
// state a Raft server must maintain.
currentTerm int //2A 服务器最后一次知道的任期号,由0递增
votedFor int //2A 该服务器投票给候选人的ID
logs []LogEntry //2 日志条目集;每一个条目包含一个用户状态机执行的指令,和收到时的任期号
commitIndex int //2 已知的最大的已经被提交的日志条目的索引值
lastApplied int //2 最后被应用到状态机的日志条目索引值(初始化为 0,持续递增)
nextIndex []int //2 对于每一个服务器,需要发送给他的下一个日志条目的索引值(初始化为领导人最后索引值加一)
matchIndex []int //2 对于每一个服务器,已经复制给他的日志的最高索引值
overtime time.Duration //2A 设置超时时间,200-400ms
timer *time.Timer //2A 每个rf中的计时器
state string //2A rf此时的状态,FOLLOWER,CANDIDATE or LEADER
voteCount int //2A 此次选举中获得的票数
}
type RequestVoteArgs struct {
// Your data here (2A, 2B).
Term int //2A 候选人的任期号
CandidateId int //2A 请求投票的候选人的ID
LastLogIndex int //2A 候选人的最后日志条目的索引值
LastLogTerm int //2A 候选人最后日志条目的任期号
}
//
// example RequestVote RPC reply structure.
// field names must start with capital letters!
//
type RequestVoteReply struct {
// Your data here (2A).
Term int //当前任期号,以便于候选人去更新自己的任期号
VoteGranted bool //是否投票给该候选人
}
AppendEntries RPC 主要实现日志条目项的复制以及心跳包的发送,在2A中,由于不需要实现条目项的复制,所以仅需实现心跳包的发送,将Entries设置为空表示心跳包。
type AppendEntriesArgs struct {
Term int //2A LEADER的任期号
LeaderId int //2A LEADER的id
PrevLogIndex int //2A 新的日志条目前的索引值
PrevLogTerm int //2A 新的日志条目前的任期号
Entries []LogEntry //2A 准备存储的日志条目(表示心跳时为空;一次性发送多个是为了提高效率)
LeaderCommit int //2A 领导人已经提交的日志的索引值
}
type AppendEntriesReply struct {
Term int //2A 当前的任期号,用于领导人去更新自己
Success bool //2A 跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的日志时为真
}
二、代码函数逻辑
1.make函数
make函数是raft.go文件的入口函数,每当test创建新的服务器时,便调用make函数,make函数主要实现两个功能,一是对Raft结构体的初始化,二是调用resetTimer函数进行计时。
func Make(peers []*labrpc.ClientEnd, me int,
persister *Persister, applyCh chan ApplyMsg) *Raft {
rf := &Raft{}
rf.peers = peers
rf.persister = persister
rf.me = me
// Your initialization code here (2A, 2B, 2C).
rf.currentTerm = 0
rf.votedFor = -1
rf.logs = make([]LogEntry, 0)
rf.commitIndex = -1
rf.lastApplied = -1
rf.nextIndex = make([]int, len(peers))
rf.matchIndex = make([]int, len(peers))
rf.overtime = time.Duration(200+rand.Intn(200)) * time.Millisecond //随机产生200-400ms
rf.state = "FOLLOWER"
rf.voteCount = 0
//DPrintf("RaftNode[%d], overtime[%d] state[%s]",rf.me, rf.overtime,rf.state)
//fmt.Println(rf.me, rf.overtime, rf.state)
rf.resetTimer()
// initialize from state persisted before a crash
rf.readPersist(persister.ReadRaftState())
return rf
}
2.resetTimer函数
重置rf的计时器,如果在overtime时间后仍然没有被重置计时器,那么将调用TimeoutElection函数进行领导人选举。
func (rf *Raft) resetTimer() {
if rf.timer != nil {
rf.timer.Stop()
}
rf.timer = time.AfterFunc(rf.overtime, func() { rf.TimeoutElection() }) //超过此服务器的overtime后,便会调用TimeoutElection函数,进行超时选举
}
3.TimeoutElection函数
超时后进行选举的函数,调用sendRequestVote函数向其他rf发送RPC。
func (rf *Raft) TimeoutElection() { //此服务器状态变为candidate,向其他服务器发布选举RPC
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.state != "LEADER" {
rf.currentTerm++ //任期++
rf.votedFor = rf.me //投票给自己
rf.voteCount++ //票数++
rf.state = "CANDIDATE" //状态变更为CANDIDATE
//向其他节点发送请求投票RPC消息,请求选举自己为LEADER
args := RequestVoteArgs{
Term: rf.currentTerm,
CandidateId: rf.me,
LastLogIndex: len(rf.logs) - 1,
}
if len(rf.logs) > 0 {
args.LastLogTerm = rf.logs[args.LastLogIndex].Term
}
for i := 0; i < len(rf.peers); i++ { //向所有其他服务器发送投票请求
if i != rf.me {
go func(server int, args RequestVoteArgs) { // 并行效率更高
var reply RequestVoteReply
ok := rf.sendRequestVote(server, &args, &reply) //进行RPC
if ok {
rf.handleVoteResult(reply) //对于获取到的结果进行处理
}
}(i, args)
}
}
}
rf.resetTimer()
}
4.RequestVote函数
rf服务器对RPC请求进行响应
//服务器对RPC投票请求进行响应
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
// Your code here (2A, 2B).
rf.mu.Lock()
defer rf.mu.Unlock()
reply.Term = rf.currentTerm
reply.VoteGranted = false
if args.Term < rf.currentTerm {
return
} else if args.Term > rf.currentTerm {
rf.currentTerm = args.Term
rf.state = "FOLLOWER"
rf.votedFor = -1
}
if rf.votedFor == -1 || rf.votedFor == args.CandidateId {
currentLogIndex := len(rf.logs) - 1
currentLogTerm := 0
if currentLogIndex >= 0 {
currentLogTerm = rf.logs[currentLogIndex].Term
}
if args.LastLogIndex < currentLogIndex || args.LastLogTerm < currentLogTerm {
return
}
rf.votedFor = args.CandidateId
reply.Term = rf.currentTerm
reply.VoteGranted = true
}
rf.resetTimer()
}
5.handleVoteResult函数
发起选举的服务器对其他服务器发送回的RPC结果进行处理,选举成为LEADER后,调用ReElection函数开始向其他节点不停地发送心跳包。
func (rf *Raft) handleVoteResult(reply RequestVoteReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
if reply.Term < rf.currentTerm {
return
}
if reply.Term > rf.currentTerm {
rf.currentTerm = reply.Term
rf.state = "FOLLOWER"
rf.votedFor = -1
rf.voteCount = 0
rf.resetTimer()
return
}
if reply.VoteGranted && rf.state == "CANDIDATE" {
rf.voteCount++
if rf.voteCount > len(rf.peers)/2 { //票数超过一半,赢得选举
rf.state = "LEADER"
for i := 0; i < len(rf.peers); i++ {
if i != rf.me {
rf.nextIndex[i] = len(rf.logs) //选举后重新初始化
rf.matchIndex[i] = -1
}
}
rf.resetTimer()
go func() { rf.ReElection() }()
}
}
}
6.ReElection函数
若该服务器是LEADER,则不停地向其他节点发送AppendEntries RPC,返回的结果通过handleAppendEntriesResult函数进行处理。
func (rf *Raft) ReElection() {
for rf.state == "LEADER" { //LEADER一直向其他节点发送心跳包
args := AppendEntriesArgs{
Term: rf.currentTerm,
LeaderId: rf.me,
PrevLogIndex: len(rf.logs) - 1,
//Entries []LogEntry
//LeaderCommit int
}
if len(rf.logs) > 0 {
args.PrevLogTerm = rf.logs[len(rf.logs)-1].Term
}
for i := 0; i < len(rf.peers); i++ {
if i != rf.me {
go func(server int, args AppendEntriesArgs) { // 并行效率更高
var reply AppendEntriesReply
ok := rf.sendAppendEntries(server, &args, &reply) //进行RPC
if ok {
rf.handleAppendEntriesResult(reply) //对于获取到的结果进行处理
}
}(i, args)
}
}
time.Sleep(time.Duration(10) * time.Millisecond) //等待10ms
}
}
7.AppendEntries函数
服务器对LEADER发送来的心跳包进行相关响应
//调用RPC函数,发送AppendEntriesRPC,由领导者发起,用来复制日志和提供心跳消息
func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {
ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
return ok
}
//服务器对AppendEntriesRPC进行响应
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
reply.Term = rf.currentTerm
if rf.currentTerm > args.Term {
reply.Success = false
return
} else {
rf.currentTerm = args.Term
rf.state = "FOLLOWER"
rf.votedFor = -1
reply.Term = rf.currentTerm
//fmt.Println(rf.currentTerm)
rf.resetTimer()
}
}
8.handleAppendEntriesResult函数
LEADER对心跳包的返回状态进行响应,若返回的任期比当前LEADER任期大,则说明当前LEADER应转为FOLLOWER。
func (rf *Raft) handleAppendEntriesResult(reply AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
if reply.Term > rf.currentTerm {
rf.currentTerm = reply.Term
rf.state = "FOLLOWER"
rf.votedFor = -1
rf.voteCount = 0
}
rf.resetTimer()
}
测试结果: