MIT-6.824 Raft lab2A

MIT-6.824 Raft lab2A

前言

本篇文章将介绍Raft 2A部分的实验内容以及其中包含的一些知识点,在paper、参考资料以及他人博客的帮助下,用了两天时间将2A部分顺利完成,其中还是历经了一些艰辛的。

前期准备

完成这个lab2A部分之前,共做了下列这些准备

实验过程

接下来便开始leader选举这一部分的代码实现了,leader选举的细节如下图所示。
在这里插入图片描述
接下来便是根据流程一步步进行代码实现了。
首先是Raft结构体的定义,Raft结构体的定义在Raft Paper中绝大部分均已给出,但在lab2A的实现中额外增加了4个参数,来辅以实现。

//
// A Go object implementing a single Raft peer.
//
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()

	currentTerm int
	votedFor    int
	logBuffer   []LogEntry

	commitIndex int
	lastApplied int

	nextIndex  []int
	matchIndex []int

	role            Role  // 角色
	heartbeat       time.Duration  // 心跳时间
	electiontimeout time.Duration  // 选举时间
	timer           *time.Timer  //定时器
	applyCh         chan ApplyMsg
}

其他的参数在论文中均给出了详细的解释,这里主要讲一下关于时间的三个参数(也是在最初比较困惑我的地方),额外的三个参数分别为心跳时间、选举时间还有定时器。
我们的选举过程是通过定时器来进行超时触发的,也就意外着我们需要有一个选举时间,除此之外,我们在leader选举完成后,leader需要通过心跳发送给其他服务器,以告诉其他服务器“我是leader,做准备吧”,因此还需要一个心跳时间。
这两个时间是不同的时间,同样,对应于不同的角色,使用也不相同,心跳时间是对leader这一角色才有的时间,因为只有leader才有主动发送消息(LogEntry)的资格,而选举时间则对于follower或者candidate而言,超时没收到消息后将会进行选举。
因此,无论是心跳还是选举,它们的机制均为定时触发,这也就是timer这一参数的作用,timer的功能变为一个定时器,通过给它设置不同的时间,它将进行定时触发。
具体使用方法如下图所示,通过select,case<-关键字来辅助当时间到了便会进行相应功能的触发。

// The ticker go routine starts a new election if this peer hasn't received
// heartsbeats recently.
func (rf *Raft) ticker() {
	for rf.killed() == false {

		select {
		// 定时器触发
		case <-rf.timer.C:
			if rf.killed() {
				return
			}
			rf.mu.Lock()
			switch rf.role {
			case ROLE_FOLLOWER:
				rf.role = ROLE_CANDIDATE
				fallthrough
			case ROLE_CANDIDATE:
				MyPrint(rf, "begin to leaderSelect")
				rf.leaderSelect()
			case ROLE_LEADER:
				rf.timer.Reset(rf.heartbeat)
				// 2A暂不需要实现
			}
			rf.mu.Unlock()
		}
	}
}

以上便是参数以及定时器的介绍。
Raft代码将通过make函数进入,进行serve的赋值及执行,这一过程我们可以在test_test.go中进行发现。
make函数主要完成初始化的操作。

func Make(peers []*labrpc.ClientEnd, me int,
	persister *Persister, applyCh chan ApplyMsg) *Raft {
	rf := &Raft{}
	rf.peers = peers
	rf.persister = persister
	rf.me = me

	rf.commitIndex = -1
	rf.currentTerm = 0
	rf.lastApplied = -1
	rf.votedFor = -1
	rf.logBuffer = make([]LogEntry, 0)
	rf.matchIndex = make([]int, len(peers))
	rf.nextIndex = make([]int, len(peers))
	rf.role = ROLE_FOLLOWER
	rf.heartbeat = 100 * time.Millisecond
	rf.resetElectionTimer()

	// Your initialization code here (2A, 2B, 2C).
	rf.applyCh = applyCh

	// initialize from state persisted before a crash
	rf.readPersist(persister.ReadRaftState())

	// start ticker goroutine to start elections
	go rf.ticker()

	return rf
}

完成初始化后,开启一个线程来执行定时器的功能,即可定时触发选举以及心跳功能了。
定时触发即为我们上述介绍到的根据服务器的timer定时时间以及角色的设置,来执行不同的功能。2A部分主要执行选举这一功能。
当触发选举功能之后,将转变状态为candidate角色,并发的向其他服务器发送投票请求。

func (rf *Raft) leaderSelect() {
	rf.currentTerm += 1
	rf.votedFor = rf.me
	voteCount := 1
	lastIndex := len(rf.logBuffer) - 1
	lastTerm := 0

	if lastIndex == -1 {
		lastTerm = 0
	} else {
		lastTerm = rf.logBuffer[lastIndex].term
	}

	voteRequestArgs := RequestVoteArgs{
		Term:         rf.currentTerm,
		CandidateId:  rf.me,
		LastLogIndex: lastIndex,
		LastLogTerm:  lastTerm,
	}

	rf.resetElectionTimer()
	for serveId, _ := range rf.peers {
		if rf.me == serveId {
			continue
		}
		go rf.voteProcess(serveId, &voteRequestArgs, &voteCount)
	}
}

候选者收到其他服务器的回复之后,将根据其他服务器的回复进行相对应的执行,直到支持者的数量占据大部分时,将会变为leader,并完成一些初始化操作。

func (rf *Raft) voteProcess(server int, args *RequestVoteArgs, voteCount *int) {
	reply := RequestVoteReply{}
	ok := rf.sendRequestVote(server, args, &reply)
	for !ok {
		// 失败重传
		if rf.killed() {
			return
		}
		ok := rf.sendRequestVote(server, args, &reply)
		if ok {
			break
		}
	}
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.currentTerm < reply.Term {
		rf.currentTerm = reply.Term
		rf.role = ROLE_FOLLOWER
		MyPrint(rf, "currentTerm < reply.term")
		return
	}
	if rf.currentTerm > reply.Term {
		MyPrint(rf, "currentTerm > reply.term")
		return
	}
	MyPrint(rf, "reply.VoteGranted is %v", reply.VoteGranted)
	if reply.VoteGranted {
		*voteCount = *voteCount + 1
		MyPrint(rf, "votecount is %v", *voteCount)
		if *voteCount > len(rf.peers)/2 && rf.role == ROLE_CANDIDATE && rf.currentTerm == args.Term {
			rf.role = ROLE_LEADER
			*voteCount = 0
			rf.nextIndex = make([]int, len(rf.peers))
			for i, _ := range rf.nextIndex {
				rf.nextIndex[i] = len(rf.logBuffer)
			}
			rf.timer.Reset(rf.heartbeat)
			return
		}
	} else {
		return
	}
}

其他服务器收到来自候选者的投票请求时,也将根据上述图中流程介绍的那样,进行对应的执行。

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.killed() {
		reply.VoteGranted = false
		reply.Term = -1
		MyPrint(rf, "server killed")
		return
	}
	if rf.currentTerm > args.Term {
		reply.VoteGranted = false
		reply.Term = rf.currentTerm
		MyPrint(rf, "currentTerm > args.Term, refuse to vote")
		return
	}
	// 状态重置
	if rf.currentTerm < args.Term {
		rf.currentTerm = args.Term
		rf.votedFor = -1
		rf.role = ROLE_FOLLOWER
	}

	logJudge := true
	logLen := len(rf.logBuffer)
	if logLen > 0 {
		if (rf.logBuffer[logLen-1].term > args.Term) ||
			(rf.logBuffer[logLen-1].term == args.Term && logLen-1 > args.LastLogIndex) {
			logJudge = false
		}
	}
	if rf.votedFor == -1 {
		if logJudge {
			rf.votedFor = args.CandidateId
			reply.VoteGranted = true
			reply.Term = rf.currentTerm
			rf.resetElectionTimer()
			MyPrint(rf, "vote successfully")
			return
		} else {
			reply.VoteGranted = false
			reply.Term = rf.currentTerm
			MyPrint(rf, "refuse to vote, logJudge is false")
			return
		}
	} else {
		// 由于网络延时等问题,再次收到选票
		if rf.votedFor == args.CandidateId {
			rf.role = ROLE_FOLLOWER
			rf.resetElectionTimer()
			MyPrint(rf, "refuse to vote, network is timeout, repeadly receive the request")
			return
		} else {
			reply.VoteGranted = false
			reply.Term = rf.currentTerm
			MyPrint(rf, "refuse to vote, voted for another service %d", rf.votedFor)
			return
		}
	}
}

以上便是2A实验部分的大体代码,除此之外,还需要完成GetState这个函数来供测试用例进行调用。
在这里插入图片描述
Finished~希望lab2B可以少点bug~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值