MIT6.824 lab2A 2023记录

labA的内容

labA的内容就是实现raft的选举leader部分,最重要的是处理三个状态之间的关系,以及超时选举和心跳模块

ticker背景部分

通过select case阻塞来判断执行哪个逻辑,阻塞channel和rpc请求部分不要加锁,不如容易死锁,或者超时等待不符合预期。非阻塞执行完逻辑后,就重启该case的定时器,如果是选举的话,就使用

func (rf *Raft) ticker() {
	for !rf.killed() {
		select {
		case <-rf.ElectionTimeOut.C:
			{
				rf.mu.Lock()
				// 开始新的选举
				log.Println("state is ", rf.Raft_Status)
				log.Println(rf.me, " start to try to get votes")
				rf.CandidateAction()
				// 重设置计时器
				rf.ResetElection()
				rf.mu.Unlock()
			}
		case <-rf.HeartBeatTimeOut.C:
			{
				rf.mu.Lock()
				if rf.Raft_Status == Leader {
					log.Println(rf.me, " start heart beat")
					// 开始心跳监测
					rf.StartHeart()
					rf.HeartBeatTimeOut.Reset(time.Duration(rf.HeartTime) * time.Millisecond)
				}
				rf.mu.Unlock()
			}
		}
		// Your code here (2A)
		// Check if a leader election should be started.
		// TODO:

		// pause for a random amount of time between 50 and 350
		// milliseconds.
		// ms := 50 + (rand.Int63() % 300)
		// time.Sleep(time.Duration(ms) * time.Millisecond)
	}
}

然后是选举超时部分

变成候选人,把自己的currentTerm自增,然后投票给自己,并行发送(注意是并行非阻塞,不需要等待所以go协程完成后再开始,这会影响我们timer)requestvote,因为我们的current_term可能在发现更大term时发生变化,所以,我们要把args放在全局区,对比当前raft状态也是用args来进行,注意要判断是不是raft变成了follwer,如果变成了follower,我们就要停止发送rpc请求,如果得到了大多数选票,我们就变成了leader,要马上发送空日志心跳来通知别的我已经是leader了,或者通过心跳发现term更大的话,我们就变成了follower(把选票清空,这个地方也是为数不多可以来重置选票为-1的地方了),同时将当前任期改为term.

func (rf *Raft) CandidateAction() {
	log.Println("start candidate")
	rf.Raft_Status = Candidate
	rf.CurrentTerm++
	log.Println(rf.CurrentTerm)
	rf.VoteFor = rf.me
	log.Println("candidate start get vote and vote itself")
	vote_cnt := 1
	// var wg sync.WaitGroup
	// wg.Add(len(rf.peers) - 1)
	args := RequestVoteArgs{
		Candidate_Curr_Term: rf.CurrentTerm,
		Candidate_Id:        rf.me,
		// Last_Log_Index:      last_idx,
		// Last_Log_Term: rf.Log_Array[last_idx].Log_Term,
	}
	for i := range rf.peers {
		if i != rf.me {
			//last_idx := len(rf.Log_Array) - 1
			go func(idx int) {
				rf.mu.Lock()
				if rf.Raft_Status != Candidate {
					rf.mu.Unlock()
					return
				}
				rf.mu.Unlock()
				reply := RequestVoteReply{}
				if rf.sendRequestVote(idx, &args, &reply) {
					rf.mu.Lock()
					defer rf.mu.Unlock()
					log.Println(rf.me, "send the rpc to the ", idx)
					// to do
					if rf.CurrentTerm == args.Candidate_Curr_Term && rf.Raft_Status == Candidate {
						log.Println("pos here")
						if reply.Vote_Granted {
							vote_cnt++
							log.Println(rf.me, "get vote numver is", vote_cnt)
							if vote_cnt > len(rf.peers)/2 {
								rf.ToLeader()
								rf.StartHeart()
								log.Println("heart sync finish, change ", rf.me, " to leader")
							}
						} else if reply.Current_Term > rf.CurrentTerm {
							log.Println("the leader to follower ", rf.me)
							rf.ToFollower()
							rf.CurrentTerm, rf.VoteFor = reply.Current_Term, -1
						}
					}
				} else {
					log.Println("rpc fail vote")
				}
				// wg.Done()
			}(i)
			// 并行发送到其他所以机器上投票

		}
	}
}

RequestVote

如果当前term大于请求方的term,时失败了,并要求发送方变成follower,或者当前term < 请求term (term相当于逻辑时钟,拥有强大的统治力)成功,当前变成follower, 或者term = 请求term,如果票被选并且不是请求方获得的话,也是失败的。当然变成follower时把他们选举时间重置一下

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	// Your code here (2A, 2B).
	log.Println("Rpc requestVote start ", rf.me)
	rf.mu.Lock()
	log.Println("Rpc get lock", rf.me)
	defer rf.mu.Unlock()
	reply.Vote_Granted = false
	if rf.CurrentTerm > args.Candidate_Curr_Term || (rf.CurrentTerm == args.Candidate_Curr_Term && rf.VoteFor != -1 && rf.VoteFor != args.Candidate_Id) {
		reply.Current_Term = rf.CurrentTerm
		reply.Vote_Granted = false
		log.Println(args.Candidate_Id, " vote grand fail", rf.me)
		return
	}
	if args.Candidate_Curr_Term > rf.CurrentTerm {
		log.Println("request is higer!!!!")
		rf.ToFollower()
		rf.VoteFor = -1
		rf.CurrentTerm = args.Candidate_Curr_Term
	}
	// -1 stand for null

	rf.VoteFor = args.Candidate_Id
	reply.Current_Term = rf.CurrentTerm
	log.Println(args.Candidate_Id, " vote grand ", rf.me)
	rf.ResetElection()
	reply.Vote_Granted = true
	// TODO: term is used for the candidate to update itself

}

HeatBeat

心跳就是重置定时器,防止follwer超时用的,还有一个就是要发现大的term,因为获得多数选票这个限制导致了一个term只能有一个leader,但是当term变大了,就可能有新的leader,然后我们把小的leader变成follwer,因为一旦有大的存在,小的就不可能在后续当leader了。这里为了保障leader的timeout不超时,我采用了心跳得到回应来重置timeout。

unc (rf *Raft) StartHeart() {
	args := AppendArgs{
		Leader_Term: rf.CurrentTerm,
		Leader_Id:   rf.me,
		// PrevLogTerm:   rf.Log_Array[rf.Next_Idx[idx]].Log_Term,
		// PrevLogIndex:  rf.Next_Idx[idx],
		Entries:       rf.Log_Array,
		Leader_Commit: rf.Committed_Idx,
	}
	for i := range rf.peers {
		if i != rf.me {
			// pre_idx := rf.Next_Idx[i]
			go func(idx int) {
				rf.mu.Lock()
				if rf.Raft_Status != Leader {
					rf.mu.Unlock()
					return
				}
				rf.mu.Unlock()
				reply := AppendReply{}
				if rf.SendAppendEntries(idx, &args, &reply) {
					rf.mu.Lock()
					defer rf.mu.Unlock()
					if !reply.Success {
						log.Println("find the leadr and change state to follower")
						rf.ToFollower()
						rf.CurrentTerm, rf.VoteFor = reply.Term, -1
					} else {
						rf.ResetElection()
					}
					// if reply.Success {
					// 	log.Println("append success", idx)
					// 	rf.Next_Idx[idx] = len(rf.Log_Array) - 1
					// 	return
					// }
					// // pre_idx == 0的情况
					// //变化为term中最后一个
					// if pre_idx > 0 && rf.Log_Array[pre_idx-1].Log_Term == rf.Log_Array[pre_idx].Log_Term {
					// 	for pre_idx > 0 && rf.Log_Array[pre_idx-1].Log_Term == rf.Log_Array[pre_idx].Log_Term {
					// 		pre_idx--
					// 	}
					// } else {
					// 	//已经是term最后一个直接,进入下一个
					// 	pre_idx--
					// }
					// rf.Next_Idx[idx] = pre_idx
				} else {
					log.Println("rpc append failed")
				}
			}(i)
		}
	}
}

AppendEntries

这和上面部分要讲的差不多,就不重复说明了

func (rf *Raft) AppendEntries(args *AppendArgs, reply *AppendReply) {
	log.Println("append ", rf.me)
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if args.Leader_Term < rf.CurrentTerm {
		reply.Term = rf.CurrentTerm
		reply.Success = false
		return
	}
	if args.Leader_Term > rf.CurrentTerm {
		rf.CurrentTerm, rf.VoteFor = args.Leader_Term, -1
	}
	// flag := false
	// for idx, entry := range rf.Log_Array {
	// 	if entry.Log_Term == args.PrevLogTerm && idx == args.PrevLogIndex {
	// 		flag = true
	// 		break
	// 	}
	// }
	// if !flag {
	// 	reply.Success = false
	// 	return
	// }
	// // 以上是为了参与者和自己的日志相同,如果不是则拒绝
	// // 确认一致的最后一条日志,然后删除这条之后的,再添加
	// if len(rf.Log_Array) > args.PrevLogIndex && rf.Log_Array[args.PrevLogIndex].Log_Term == args.PrevLogTerm {
	// 	rf.Log_Array = rf.Log_Array[:args.PrevLogIndex+1]
	// }
	// // to d
	// rf.Log_Array = append(rf.Log_Array, args.Entries[args.PrevLogIndex+1:]...)
	// if args.Leader_Commit > rf.Committed_Idx {
	// 	rf.Committed_Idx = int(math.Min(float64(args.Leader_Commit), float64(len(rf.Log_Array))))
	// }
	rf.ToFollower()
	rf.ResetElection()
	rf.CurrentTerm = args.Leader_Term
	reply.Success = true
	//TODO:2,3,4,5 in the paper

}

基于分布式或者高竞争状况下使用锁和结构的思考

通过阅读官网给出的tips,如果term++和转变state如果不应该让别人看见的话,我们就要把这整个段上锁,这有点像事务一致性,不暴露中间状态。同时rpc和channel请求时要解锁,防止互相等待对方的锁,或者阻塞过长,导致timer时间不符合预期等情况。

结构

go可以通过锁和共享数据,或者channel来传递信息,我们尽量分开任务在协程里面,这样减少了超时时间的花费。不要用ticker(最简单的方法是使用 time.Sleep() 和一个小常量参数来驱动定期检查。不要使用 time.Ticker 和 time.Timer;它们很难正确使用。)

同时我们要注意到 网络可以延迟 RPC 和 RPC 回复,并且当 您发送并发 RPC 时,网络可以重新排序请求和 回复。可能后面的实验会遇到这些问题

正确性

测试了300次,也可能还是有bug:( 。。。。。更新:把ticker改成了time来循环监测,同时把heart和elect分成两个部分go协程,降低了延迟。这样符合了hint要求,对架构更放心,并且测试了600次。不要用ticker(最简单的方法是使用 time.Sleep() 和一个小常量参数来驱动定期检查。不要使用 time.Ticker 和 time.Timer;它们很难正确使用。)

参考资料:https://pdos.csail.mit.edu/6.824/labs/raft-locking.txt

                  https://pdos.csail.mit.edu/6.824/labs/raft-structure.txt

                  Students' Guide to Raft :: Jon Gjengset

                   MIT6.824-2021/lab2.md at master · OneSizeFitsQuorum/MIT6.824-2021 · GitHub

                        

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值