mit6.824raft 2A

总体思路

lab2A主要实现raft中leader选举问题,server中的角色有三种,leader、candidate,follower,它们各自职责如下:

  1. follower
    a. 对candidate和leader的rpc进行回复
    b. 如果超市时间内没有收到AppendEntries rpc 或者 收到candidate的投票,会讲自己的 状态转为candidate
    2.candidate
    a. 增加当前peer的term
    b. 为自己投票
    c. 重置选举超市时间
    d. 发送RequestVote RPC 发送给其他peer
    3.leader
    如果发送的rpc收到的回复大多数都认可自己,那就变成leader
    如果收到了AppendEntries RPC, 那就变成follower,说明有其他人在选举
    如果超时时间过期了,那就开启一个新的term

状态间的转换
2A主要包含处理来自leader和candidate的rpc请求AppendEntries、RequestVote;leader发送log同步和heartbeat请求;candidate发送选举请求

执行流程

首先完成raft实例初始化
开启线程用于维持自身状态

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.

	state              int32         //0:follwer 1:candidate 2:leader
	currentTerm        int           // 当前最新任期号
	voteFor            int           //收到的投票的候选人id,没投就为空
	log                []Log         //log条目
	commitIndex        int           //已提交的最大的log条目索引
	lastApplied        int           //应用于状态机的最大索引条目
	nextIndex          int           //leader要发给server的下一个条目索引
	matchIndex         int           //该服务器上最大的条目索引
	electionTimeout    time.Duration //选举超时时间,选举时随机初始化
	getVotes           int           //候选人获得票数
	startTime          time.Time     //开始时间
	voteFinish         chan bool     //投票已完成
	rpcRequest         chan bool     //收到rpc请求
	isSendVoteFinished bool          //是否发送了选举完成channel
}
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).
	// initialize from state persisted before a crash
	rf.readPersist(persister.ReadRaftState())
	rf.state = 0
	rf.currentTerm = 0
	rf.voteFor = -1
	rf.rpcRequest = make(chan bool, 10)
	rf.voteFinish = make(chan bool, 10)
	fmt.Println(len(peers), "peers")
	go rf.ExeTask()
	fmt.Println(me, "begin work")
	return rf
}

然后是RequestVote

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	// Your code here (2A, 2B).
	//对raft加锁
	rf.mu.Lock()
	fmt.Println(rf.me, "receive vote request from", args.CandidateId, "at", rf.startTime)
	if args.Term > rf.currentTerm {
		rf.voteFor = -1 //有更新的term初始化为未投
	}
	if args.Term < rf.currentTerm {
		//任期比我小不给投
		reply.VoteGranted = false
		reply.Term = rf.currentTerm
		return
	} else {
		if args.Term > rf.currentTerm {
			rf.state = 0
			rf.currentTerm = args.Term
		}

		if rf.voteFor == -1 || rf.voteFor == args.CandidateId {
			//条件满足可以投票
			reply.VoteGranted = true
			reply.Term = rf.currentTerm
			rf.state = 0
			rf.voteFor = args.CandidateId
			rf.rpcRequest <- true
			fmt.Println(rf.me, "vote for ", args.CandidateId, "term is", args.Term, "at", time.Now())
		}
	}
	rf.mu.Unlock()
}

RequestVote处理逻辑如下:
1.如果请求的周期大于本身的周期,不管什么状态,本身角色转为follow,并且周期变成更大的
2.如果请求周期小于自身周期,则直接返回
3.rf.votedFor == -1 || rf.votedFor == args.CandidateId raft论文中要求的写法,-1表示当前任期还未投票
4.最后投票成功后,重新设置自身选举时间,然后投票成功

AppendEntries实现思路

func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
	rf.mu.Lock()
	if rf.currentTerm <= args.Term {
		rf.rpcRequest <- true
	}
	fmt.Println(rf.me, "receive heartbeat from", args.LeaderId, "at", rf.startTime)
	reply.Term = rf.currentTerm
	if args.Term < rf.currentTerm {
		reply.Success = false
	} else {
		//检查条目是否匹配
		if rf.state != 0 {
			rf.state = 0 //有更新的term,自己要成为follwer
		}
		rf.currentTerm = args.Term
		if len(rf.log) <= args.PrevLogIndex {
			reply.Success = false
		} else {
			if rf.log[args.PrevLogIndex].Term == args.PrevLogTerm {
				reply.Success = true
				rf.currentTerm = args.Term
			} else {
				//如果索引一样,term不一样删除当前索引之后的条目
				rf.log = rf.log[:args.PrevLogIndex]
				reply.Success = false
			}
		}
	}
	rf.mu.Unlock()
}

heartbeat处理逻辑:
1.如果参数周期大于自身,则转follow
2.若小于,则带着更大的周期返回
3.心跳发送成功,更新领导人

接下来就是当follower转变为candidate发送选举请求
部分代码实现(全放有点长):

//先给自己投票并且将任期+1
rf.mu.Lock()
rf.currentTerm++
rf.voteFor = rf.me
rf.mu.Unlock()
//接下来初始化rpc参数等信息代码略...
//votges用来统计票数
for i, _ := range rf.peers {
		//并行的向每个server发送请求
		if i != me {
			go rf.sendRequestVote(i, &args, &votes)
		}
	}

	select {
	case <-rf.voteFinish://完成投票
	case <-time.After(time.Duration(rand.Intn(500+1)+500) * time.Millisecond)://选举超时
		rf.mu.Lock()
		rf.state = 1
		rf.mu.Unlock()
	}
	
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, votes *int) {
	reply := RequestVoteReply{}
	ok := rf.peers[server].Call("Raft.RequestVote", args, &reply)
	if rf.isSendVoteFinished {
		return
	}
	if ok {
		if reply.VoteGranted {
			*votes++
			if (*votes) > len(rf.peers)/2 {
				rf.mu.Lock()
				rf.state = 2
				rf.isSendVoteFinished = true
				rf.mu.Unlock()
				rf.voteFinish <- true
				fmt.Println(rf.me, "is leader term is", rf.currentTerm, "at", time.Now())
				//成为leader立马发送heartbeat
				go rf.SendHeartbeat()
			}
		} else if reply.Term > rf.currentTerm {
			rf.mu.Lock()
			rf.state = 0
			rf.currentTerm = reply.Term
			rf.isSendVoteFinished = true
			rf.mu.Unlock()
			rf.voteFinish <- true
		}
	}
	return
}

在投票选举的过程中如果出现了票数过半的情况就直接改变状态声明选举完成,跳到leader代码发送heartbeat

最后是发送heartbeat逻辑:

func (rf *Raft) SendHeartbeat() bool {
	args := AppendEntriesArgs{}
	args.LeaderId = rf.me
	rf.mu.Lock()
	args.Term = rf.currentTerm
	myterm := rf.currentTerm
	me := rf.me
	rf.mu.Unlock()
	replyNum := 0
	reply := AppendEntriesReply{}
	fmt.Println(rf.me, "send heartbeat request at", time.Now())
	for i, _ := range rf.peers {
		if i != me {
			if rf.sendAppendEntries(i, &args, &reply) {
				replyNum++
				if reply.Term > myterm {
					rf.mu.Lock()
					rf.state = 0
					rf.currentTerm = reply.Term
					rf.mu.Unlock()
					return false
				}
			} else {
				//fmt.Println(me, "can not send heartbeat to", i)
			}
		}
	}
	if replyNum < len(rf.peers)/2 {
		rf.mu.Lock()
		rf.state = 0
		rf.mu.Unlock()
	}
	return true
}

发送心跳时当返回有更大的term则直接变为follower

注意细节

以上为2A的主要内容,因为刚开始学习分布式,go语言也不熟悉,所以刚开始走了很多弯路,代码中有不对的地方请各位批评指正。经过测试改代码大部分情况下都能通过测试(不能保证每次都能通过测试)主要出现以下问题:

多个候选人同时发起选举导致term一直增加,无法得到leader

第二种情况是当得到leader后立马又有新的候选人出现成为新的leader
这种情况是当某个follower给某个candidate投票后,candidate成为了leader,但此时follower正好超时了所以follower会变为candidate发起选举请求成为新的leader

在实现过程中超时管理的实现是一个比较头疼的问题(并发程序的代码真不好调啊…)
开始的时候我是直接采用设置一个startTime time.Time变量然后每次有rpc请求时就跟新startTime 然后time.Sleep()一段时间最后检查当前时间点与startTime的差有没有超出设置的时间,结果测试的时候当有网络崩溃的时候要不就没法选出leader要不就有多leader产生,所以后面再实现的时候采用select+time.After()就好多了,不得不说go这方面还是好用的。

select {
			case <-rf.rpcRequest:
			case <-time.After(time.Duration(rand.Intn(200+1)+200) * time.Millisecond):
				rf.mu.Lock()
				rf.state = 1
				fmt.Println(rf.me, "become candidate at", time.Now())
				rf.mu.Unlock()
	}

还有一点要注意的是论文中提到的超时时间大小的设置问题(这可能是导致无法选出leader的原因),另外在发送选举请求时当有超过半数票时就立马宣布选举完成并发送heartbeat不然可能会有新的follower超时。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值