tinykv Project2aa 实现思路

tinykv 项目地址https://github.com/talent-plan/tinykv

本博客提供一个参考思路,未必是正确答案(但能够通过测试集),请注意甄别。欢迎在评论区讨论。


Project2a 的整体目标是实现 Raft 算法。作为其子任务,Project2aa 实现其中的领导人选举(Leader Election)部分。

需要修改的文件:

  • raft/raft.go

首先我们需要完成函数newRaft,用于创建一个全新的 Raft 结点。下面是一个简单的实现,提供了 Raft 的基本要素,主要包括StateheartbeatTimeoutelectionTimeoutvotes等。原文档没有解释清楚,但是可以看出,Raft.Vote表示这个 Raft 给谁投了票,而Raft.vote记录了谁给这个 Raft 投了票。这里值得一提的是electionElapsed成员的初始化,原论文指出一个每次成为 follower(包括 Raft 新建立时)或者 candidate,都需要随机选取一个选举超时间隔,从而最大限度地避免多个 Raft 同时成为 candidate 的情况;但是原论文中没有说明具体要怎么去实现这个“随机”。raft_test.go中的测试则隐约指出了这个随机的含义,基于 Raft 的成员electionTimeOut,每次随机确定的选举超时间隔被规定在区间[electionTimeOut, 2 * electionTimeOut)。在代码中,采用将electionElapsed随机初始化到区间(-electionTimeOut, 0]的方式实现相同的功能。

// newRaft return a raft peer with the given config
func newRaft(c *Config) *Raft {
	if err := c.validate(); err != nil {
		panic(err.Error())
	}
	// Your Code Here (2A).
	raft := &Raft{id: c.ID,
		State:            StateFollower,
		heartbeatTimeout: c.HeartbeatTick,
		electionTimeout:  c.ElectionTick,
		electionElapsed:  -rand.Intn(c.ElectionTick),
		Prs:              make(map[uint64]*Progress),
		votes:            make(map[uint64]bool),
	}
	for _, pr := range c.peers {
		raft.votes[pr] = false
	}
	return raft
}

当然,上面只是对newRaft函数的初步实现,在 Project2ab 和后续的任务中肯定需要对其进行修改。

接下来,我们还要实现函数sendHeartbeat函数。实现方式是在对应 Raft 的msgs(消息箱)中添加一个心跳类型的消息,代码如下所示。

// sendHeartbeat sends a heartbeat RPC to the given peer.
func (r *Raft) sendHeartbeat(to uint64) {
	// Your Code Here (2A).
	if r.State != StateLeader {
		return
	}
	r.msgs = append(r.msgs, pb.Message{
		MsgType: pb.MessageType_MsgHeartbeat,
		From:    r.id,
		To:      to,
		Term:    r.Term,
	})
}

类似地,实现一个sendRequestVote函数。这是我自己增加的函数,后面会用到。

// newly added: sendRequestVote sends a RequestVote RPC to the given peer.
func (r *Raft) sendRequestVote(to uint64) {
	if r.State != StateCandidate {
		return
	}
	r.msgs = append(r.msgs, pb.Message{
		MsgType: pb.MessageType_MsgRequestVote,
		From:    r.id,
		To:      to,
		Term:    r.Term,
	})
}

随后,实现tick函数,这个函数的功能是使逻辑时钟计时一个滴答。对于所有的 State,electionElapsed都要加一;而 leader 还额外维护了一个heartbeatElapsed,这时它也要加一。可能出现下面两种行动:

  • leader:在心跳超时间隔完成时,向其他所有 Raft 发出心跳。具体做法是将发送给其他 Raft 的心跳类型信息放入自己的信箱。
  • candidate/follower:在选举超时间隔完成时,成为新一轮(Term 自增)的 candidate,并且向其他结点发送 RequestVote RPC 消息。通过处理(Step)本地的举手消息(MsgHup),启动新一轮的选举并发送 RequestVote RPC。
// tick advances the internal logical clock by a single tick.
func (r *Raft) tick() {
	// Your Code Here (2A).
	r.electionElapsed++
	if r.State == StateLeader {
		r.heartbeatElapsed++
		if r.heartbeatElapsed >= r.heartbeatTimeout {
			r.heartbeatElapsed = 0
			r.Step(pb.Message{MsgType: pb.MessageType_MsgBeat})
		}
	} else if r.electionElapsed >= r.electionTimeout {
		r.electionElapsed = -rand.Intn(r.electionTimeout)
		r.Step(pb.Message{MsgType: pb.MessageType_MsgHup})
	}
}

接下来完成函数becomeFollower,描述一个 Raft 结点如何成为 follower 成员。需要注意的是,当一个结点称为新 Term的 follower 时,需要清除当前 Term 的选举记录 Vote。

// becomeFollower transform this peer's state to Follower
func (r *Raft) becomeFollower(term uint64, lead uint64) {
	// Your Code Here (2A).
	if term != r.Term {
		// new term, currently votes for nobody
		r.Vote = 0
	}
	r.Term = term
	r.State = StateFollower
	r.Lead = lead
	r.electionElapsed = -rand.Intn(r.electionTimeout)
}

然后完成函数becomeCandidate,描述一个 Raft 结点如何成为 candidate 成员。一个 Raft 在成为 candidate 时,会清楚当前 Term 其它结点给自己的投票结构(votes),并且进入新的 Term。在新 Term 中,Raft 立刻给自己投票。需要注意的是,如果当前 cluster 中只有自己一个 Raft 结点,它会立刻成为 leader。

// becomeCandidate transform this peer's state to candidate
func (r *Raft) becomeCandidate() {
	// Your Code Here (2A).
	r.Term++
	r.Vote = r.id
	r.State = StateCandidate
	for pr, _ := range r.votes {
		if pr == r.id {
			r.votes[pr] = true
		} else {
			r.votes[pr] = false
		}
	}
	r.electionElapsed = -rand.Intn(r.electionTimeout)

	// extreme case: when the cluster has only 1 raft, it become leader immediately.
	if len(r.votes) == 1 {
		r.becomeLeader()
	}
}

之后完成函数becomeLeader,描述一个 Raft 结点如何成为一个 leader 成员。这个函数让我们注意,leadre 需要在它的任期发送一个空 Entry,但是这涉及到很多 Project2ab 的内容,我们在 Project2aa 中可以暂且不实现这个功能。

// becomeLeader transform this peer's state to leader
func (r *Raft) becomeLeader() {
	// Your Code Here (2A).
	// NOTE: Leader should propose a noop entry on its term
	r.State = StateLeader
	r.Lead = r.id
	r.heartbeatElapsed = 0
	// ignore entries temperarily
}

然后就是核心函数Step了。一个 Raft 调用这个函数,用于处理本地的或者来自其他 Raft 的消息,以作出相应改变。这里我们没有对MsgProposeMsgAppend等类型的消息进行处理,将放在后面的任务中完成。注意:

  • 当一个 Raft 收到的消息中的 Term 大于自己当前的 Term 时,需要更新自己的 Term。
  • candidate/follower 收到 MsgHup 消息时,会开启新的选举;而 leader 保持现状。
// Step the entrance of handle message, see `MessageType`
// on `eraftpb.proto` for what msgs should be handled
func (r *Raft) Step(m pb.Message) error {
	// Your Code Here (2A).

	// undone:
	// MsgPropose 2
	// MsgAppend 3: half completed
	// MsgAppendResponce 4
	// MsgSnapshot 7
	// MessageType_MsgTransferLeader 11
	// MessageType_MsgTimeoutNow 12

	if m.Term > r.Term && m.MsgType != pb.MessageType_MsgAppend && m.MsgType != pb.MessageType_MsgRequestVote {
		r.becomeFollower(m.Term, r.Lead)
		return nil
	}
	switch r.State {
	case StateFollower:
		switch m.MsgType {
		case pb.MessageType_MsgHup:
			r.startNewElection()
		case pb.MessageType_MsgRequestVote:
			r.handleRequestVote(m)
		case pb.MessageType_MsgHeartbeat:
			r.handleHeartbeat(m)
		case pb.MessageType_MsgAppend:
			r.handleAppendEntries(m)
		}
	case StateCandidate:
		switch m.MsgType {
		case pb.MessageType_MsgHup:
			r.startNewElection()
		case pb.MessageType_MsgRequestVote:
			r.handleRequestVote(m)
		case pb.MessageType_MsgRequestVoteResponse:
			if m.Term == r.Term {
				r.votes[m.From] = !m.Reject
				if r.canBecomeLeader() {
					r.becomeLeader()
				}
			}
		case pb.MessageType_MsgHeartbeat:
			r.handleHeartbeat(m)
		case pb.MessageType_MsgAppend:
			r.becomeFollower(m.Term, r.id)
			r.handleAppendEntries(m)
		}
	case StateLeader:
		switch m.MsgType {
		// note that MessageType_MsgHup wont't cause a leader to start a new election.
		// the leader remains its term.
		case pb.MessageType_MsgBeat:
			for pr, _ := range r.votes {
				if pr != r.id {
					r.sendHeartbeat(pr)
				}
			}
		case pb.MessageType_MsgRequestVote:
			r.handleRequestVote(m)
		case pb.MessageType_MsgHeartbeat:
			r.handleHeartbeat(m)
		case pb.MessageType_MsgAppend:
			r.handleAppendEntries(m)
		}
	}
	return nil
}

上面的Step函数中调用了很多其他的函数。比如自定义的startNewElection,其定义如下。

// newly added: startNewElection starts a new election, including turning the raft state to StateCandidate and send RequestVote RPC to other nodes
func (r *Raft) startNewElection() {
	r.electionElapsed = -rand.Intn(r.electionTimeout)
	r.becomeCandidate()
	for pr, _ := range r.votes {
		if pr != r.id {
			r.sendRequestVote(pr)
		}
	}
}

以及自定义的handleRequestVote

// newly added: handleRequestVote handles RequestVote RPC request
func (r *Raft) handleRequestVote(m pb.Message) {
	if m.MsgType != pb.MessageType_MsgRequestVote {
		return
	}
	if m.Term < r.Term || (m.Term == r.Term && r.Vote != 0 && r.Vote != m.From) {
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgRequestVoteResponse,
			From:    r.id,
			To:      m.From,
			Term:    r.Term,
			Reject:  true,
		})
	} else {
		r.becomeFollower(m.Term, m.From)
		r.Vote = m.From
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgRequestVoteResponse,
			From:    r.id,
			To:      m.From,
			Term:    m.Term,
		})
	}
}

还有handleHeartbeat函数:

// handleHeartbeat handle Heartbeat RPC request
func (r *Raft) handleHeartbeat(m pb.Message) {
	// Your Code Here (2A).
	if m.Term < r.Term {
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgHeartbeatResponse,
			From:    r.id,
			To:      m.From,
			Term:    r.Term,
			Reject:  true,
		})
	} else {
		r.becomeFollower(m.Term, m.From)
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgHeartbeatResponse,
			From:    r.id,
			To:      m.From,
			Term:    m.Term,
		})
	}
}

然后是canBecomeLeader。我记得论文还是哪个地方要求一个 candidate 在收到一半的拒绝时应该退回到 follower,但是暂且没实现这个功能。

// newly added: canBecomeLeader counts the supportive votes received and judge if a raft can become the leader
func (r *Raft) canBecomeLeader() bool {
	if r.State != StateCandidate {
		return false
	}
	count := 0
	for _, support := range r.votes {
		if support {
			count++
			if 2*count > len(r.votes) {
				return true
			}
		}
	}
	return false
}

随后是两个自带的函数,实现如下。其中handleAppendEntries的实现显然是不完整的,它涉及到 RaftLog 的更新,这个将在后面的项目中继续实现。

// handleAppendEntries handle AppendEntries RPC request
func (r *Raft) handleAppendEntries(m pb.Message) {
	// Your Code Here (2A).
	if m.MsgType != pb.MessageType_MsgAppend {
		return
	}
	if m.Term < r.Term {
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgAppendResponse,
			From:    r.id,
			To:      m.From,
			Term:    r.Term,
			Reject:  true,
		})
	} else {
		r.becomeFollower(m.Term, m.From)
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgAppendResponse,
			From:    r.id,
			To:      m.From,
			Term:    m.Term,
		})
	}
	// need more complements to append entries to RaftLog.
}

// handleHeartbeat handle Heartbeat RPC request
func (r *Raft) handleHeartbeat(m pb.Message) {
	// Your Code Here (2A).
	if m.Term < r.Term {
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgHeartbeatResponse,
			From:    r.id,
			To:      m.From,
			Term:    r.Term,
			Reject:  true,
		})
	} else {
		r.becomeFollower(m.Term, m.From)
		r.msgs = append(r.msgs, pb.Message{
			MsgType: pb.MessageType_MsgHeartbeatResponse,
			From:    r.id,
			To:      m.From,
			Term:    m.Term,
		})
	}
}

至此,Project2aa 也就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值