6.824 lab2A

引言

恰逢寒假准备实习,便重新拾起6.824的lab,lab2主要是对Raft论文的复现,本文实现了2A部分的内容,lab2的内容难度还是很大的,写下此文便于日后复习使用。

Reference:

Raft论文翻译

MIT 6.824 Lab2 翻译 

Raft 算法详解(一)领导者选举

MIT 6.824 lab2 PartA

正文

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()
}

测试结果:

 

 

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值