6.824 Lab2 PartA实验部分

本文深入分析了raft一致性算法在选举过程中的关键步骤,包括服务器集群的初始化、领导者选举、网络故障模拟及恢复、心跳机制等。在测试场景中,详细阐述了如何确保选举的正确性和容错性,如处理节点断线重连、选举超时、投票冲突等问题,并探讨了代码实现中的优化技巧,如避免for循环空转和提高RPC调用效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试文件测试内容分析

func TestReElection2A(t *testing.T) {
	servers := 3
	cfg := make_config(t, servers, false)
	defer cfg.cleanup()

	cfg.begin("Test (2A): election after network failure")

	leader1 := cfg.checkOneLeader()

	// if the leader disconnects, a new one should be elected.
	cfg.disconnect(leader1)
	cfg.checkOneLeader()

	// if the old leader rejoins, that shouldn't
	// disturb the new leader.
	cfg.connect(leader1)
	leader2 := cfg.checkOneLeader()

	// if there's no quorum, no leader should
	// be elected.
	cfg.disconnect(leader2)
	cfg.disconnect((leader2 + 1) % servers)
	time.Sleep(2 * RaftElectionTimeout)
	cfg.checkNoLeader()

	// if a quorum arises, it should elect a leader.
	DPrintf("Check two server can choose a leader")
	cfg.connect((leader2 + 1) % servers)
	cfg.checkOneLeader()

	// re-join of last node shouldn't prevent leader from existing.
	cfg.connect(leader2)
	cfg.checkOneLeader()

	cfg.end()
}

整体测试流程如下:

1、开启3个服务器,检查能否选举出一个leader

2、一个leader掉线,看能否选出一个新的leader

3、旧的leader回归,不应该影响新的leader的状态(当然旧leader迅速被重新选举为leader也没问题)

4、下线两个服务器,此时不应该有leader

5、恢复一个机器,此时有两个机器。应该选举出一个leader

6、恢复下线机器,三台服务器同时可用,此时原有leader的状态不应该被影响

需要推敲的问题

1、断线的旧leader的回来尚未接收到发送的心跳就直接开始新的选举是否有问题

答:没问题,是运行的正常逻辑

2、lastActiveTime在哪些时候进行更新比较好?

答:应该严格遵守figure2的实现流程

If election timeout elapses without receiving AppendEntries RPC from current leader or granting vote to candidate:

只有在接收到当前leader的AppendEntries请求或者批准投票时才会对lastActiveTime进行更新

3、第五步通常会出现新来和回归的机器同时开始选举,怎么办?

答:此时第一次选举应该失败,随后因为二者的election Timeout不同,会自动开启新一轮选举得到最终结果。

4、选举的leader应该将广播时间设置比较早,以便迅速出发发送心跳的服务

核心代码示例

func (rf *Raft) startElection() {
	rf.mu.Lock()
	defer rf.mu.Unlock()
	now := time.Now()
	elapses := now.Sub(rf.lastActiveTime)
	// 超时的话会从Follower转为Candidate
	if rf.role == Follower {
		if elapses >= rf.electionTimeOut {
			rf.role = Candidate
			DPrintf("RaftNode [%d] changed from follower to candidate at term %d", rf.me, rf.currentTerm)
		}
	}
	if rf.role == Candidate && elapses >= rf.electionTimeOut {
		rf.lastActiveTime = now
		rf.currentTerm += 1
		rf.votedFor = rf.me
		rf.persist()
		// 准备开始发起投票,构造投票流程
		args := RequestVoteArgs{
			Term:         rf.currentTerm,
			CandidateId:  rf.me,
			LastLogIndex: len(rf.logs),
			// go不支持三元表达式,所以term赋值写在最后
		}
		lastLogTerm := 0
		if len(rf.logs) != 0 {
			lastLogTerm = rf.logs[len(rf.logs)-1].Term
		}
		args.LastLogTerm = lastLogTerm
		// 参数赋予流程初步结束,为了避免RPC请求停顿影响效率,先放掉锁
		rf.mu.Unlock()
		// 开始for循环不断发起响应通知进行处理
		cond := sync.NewCond(&rf.mu)
		// 初始化时全部peer里自己就已经给自己投了一票,因此初始化为1
		finished := 1
		count := 1
		for index, _ := range rf.peers {
			if index == rf.me {
				continue
			}
			// 发送请求并处理返回结果
			go func(server int, args *RequestVoteArgs) {
				reply := RequestVoteReply{}
				ok := rf.sendRequestVote(server, args, &reply)
				rf.mu.Lock()
				finished += 1
				DPrintf("RaftNode [%d] send Rpc to RaftNode [%d] and get response,%v,now finish %d", rf.me, server, ok, finished)
				if ok {
					// 进行逻辑运行,除了降级逻辑外,还是尽量将逻辑推迟到下面
					if reply.VoteGranted == true {
						count += 1
					} else {
						if reply.Term > rf.currentTerm {
							// 状态进行改变清空
							rf.currentTerm = reply.Term
							rf.role = Follower
							rf.votedFor = -1
							rf.leaderId = -1
							rf.persist()
						}
					}
					DPrintf("RaftNode [%d] get result %d,vote %d", rf.me, finished, count)
				}
				cond.Broadcast()
				rf.mu.Unlock()
			}(index, &args)
		}
		rf.mu.Lock()
		// 想要直接往后走:1、投票完毕 2、得到超过半数票数无需继续等待 3、已经不再是candidate状态
		// 注意小于等于与大于等于的使用,一开始在这里卡死便是因为小于等于直接满足
		for count <= len(rf.peers)/2 && finished != len(rf.peers) && rf.role == Candidate {
			DPrintf("finish %d,count %d", finished, count)
			cond.Wait()
		}

		// 不是candidate的话直接结束
		if rf.role != Candidate {
			return
		}
		DPrintf("boolean %v,count %d,total %d", count > len(rf.peers)/2, count, len(rf.peers))
		// 否则对状态进行判断
		if count > len(rf.peers)/2 {
			rf.role = Leader
			rf.leaderId = rf.me
			rf.lastBroadcastTime = time.Unix(0, 0) // 设置为原始时间,保证立刻发送心跳
			rf.persist()
			DPrintf("RaftNode  [%d] become a leader", rf.me)
		}
	}
	return
}

在写的时候本人遇到的错误点如下:

1、在写逻辑表达式、if判断条件时一定要注意大于等于与大于之类的区别。

开始写成count<len(peers)/2,导致一开始直接满足条件跳过cond.wait所处的for循环,永远输出错误结果。实际上应该为count<=len(peers)/2

2、第一遍完成后遇到了split-brain问题,两个都获得了一票,总票数不等于len(peers)且没有人得票超过一半陷入死锁状态

解决方法:将rf.mu.lock从if ok块里提出来,放到rpc结果后。如果机器下线,RPC结果ok为false,也可以进行统计

编程技巧

1、尽可能使用cond.wait等方法来避免for循环空转浪费CPU资源

2、RPC调用前不要加锁,等返回结果后再加锁处理结果,以避免因为网络因素长时间无法得到结果程序无法释放出锁,影响整体执行效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值