从这一部分开始,我们的代码量开始陡然增大。为了避免所有代码都挤在一个 raft.go 文件中,我们将其按逻辑进行拆分:

  1. 领导选举逻辑从raft.go 中拆出来,命名为 raft_election.go
  2. 日志复制逻辑从 rat.go 中拆出来,命名为 raft_replication.go
  3. 所有其他公用的逻辑剩下在 raft.go

注:在 golang 中,同一个类的实现拆到多个文件中时,其他文件通常使用该类名作为前缀,在我们这,就是 raft_XX.go

选举拆分

选举部分主要包括我们在 PartA 中提到的几部分:RPC 发送方的三个层次以及 RPC 接收方的回调函数。

1
2
3
4
5
6
7
// functions in Candidate(RPC sender)
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool
func (rf *Raft) startElection(term int)
func (rf *Raft) electionTicker()

// callback in Peer(RPC receiver)
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply)

此外,还有和他们关联的两个 RPC 的结构体:

1
2
type RequestVoteArgs struct {}
type RequestVoteReply struct {}

和一些辅助函数:

1
2
3
// election timer checker and reset
func (rf *Raft) resetElectionTimerLocked()
func (rf *Raft) isElectionTimeoutLocked() bool

复制拆分

和领导者选举逻辑一样,也是主要包括 RPC 发送方的三个层次和 RPC 接收方的回调函数:

1
2
3
4
5
6
7
// functions in Leader(RPC sender)
func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool
func (rf *Raft) startReplication(term int) bool
func (rf *Raft) replicationTicker(term int)

// callback in Peer(RPC recevier)
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply)

RPC 关联结构体:

1
2
type AppendEntriesReply struct {}
type AppendEntriesArgs struct {}

剩余逻辑

剩下的逻辑,主要包括:

  1. 一些公用的函数和配置:如状态机转换函数,contextLost 判断函数等等
  2. 对外提供的接口:如 Start()Make() 等函数
  3. 暂时用不到的函数:如 PartC 中 persist 相关函数

就留在 raft.go 文件中了。