1. 综述
在本次实验中,我使用Go语言在给定框架上实现了简单的Raft协议,通过实现包括节点选举、心跳机制、日志追加和持久化等内容,完成了Part1,Part2和Part3。项目托管在github上,地址为NJU-DisSys-2020。当实验DDL过了后我将开源。
2. 相关定义
根据Raft的原始论文以及扩展版论文,我定义了如下常量和数据结构。
2.1 常量定义
2.1.1 状态常量
首先是定义Raft中Sever的三种状态,分别是
- 领导者(Leader)
- 跟随者(Follower)
- 候选者(Candidate)
// Raft中Server的三种状态
const (
LEADER = iota
FOLLOWER
CANDIDATE
)
2.1.2 时间常量
根据PPT,论文以及课程实验来源MIT6.824中lab2的要求,指定如下的时钟相关常量。
- 选举超时间隔 100-500ms
- 心跳间隔100ms
// 时钟相关的
const (
ElectionTimeoutMin = 100
ElectionTimeoutMax = 500
HeartBeatsInterval = 100
)
2.1.3 日志常量
最后,额外定义一个特殊值Backoff
用于日志的追加发生异常时的处理
// AppendLog失败后是否需要回退一个Term
const (
BackOff = -100
)
2.2 数据结构定义
2.2.1 日志定义
首先,根据论文要求,定义如下的LogEntry
表示在Raft中各个节点中的log entry
type LogEntry struct {
Command interface{
} // 状态机的命令
Term int // log entry的term
Index int // log entry的index
}
为了方便使用,我们额外记录了一个Index域,而不是使用数组的下标来表示Index。但是在最后实际系统实现中,由于往log数组中加入了一个空log,使得log数组的下标与Index值相等应该是一个需要被满足的循环不变量。那么可以用这个性质来Debug。
2.2.2 Raft结构体定义
根据实验需要,我定义了如下Raft的结构体。除了框架代码给出了的内容,Raft结构体中主要还有如下内容:
所有服务器中的持久化状态
这部分内容是在Part3中将被持久化的状态,即使节点Crash后,也应该从持久化的设置中恢复。
// Persistent state on all servers
currentTerm int // server已知的最新term,初始化为0,单调递增
votedFor int // 当前term中所投票的id,如果没有投票,则为null
log []LogEntry // 实际日志的下标从1开始
所有服务器中的不稳定状态
// Volatile state on all servers
commitIndex int // committed的最大的log entry index,初始化为0,单调递增
lastApplied int // 应用到状态机的最大的log entry index,初始化为0,单调递增
Leader中的不稳定状态
nextIndex []int // 对每个server下一个要发送的log entry的序号,初始化为 leader last log index+1
matchIndex []int // To replicated,对每个server,已知的最高的已经复制成功的序号
实验相关的辅助字段
除了论文Figure2所给内容外,为了实现选举,心跳机制和日志追加等内容,我还给Raft结构体增加了如下字段。
其中,通过Go语言中的time.Timer
提供的计时超时功能,实现选举以及心跳(日志追加)。
// Self defined
role int // 服务器状态
leaderID int // Follower的Leader
electionTimer *time.Timer // Leader Election的定时器
heartBeatTimer *time.Timer // Heart Beat的定时器
applyCh chan ApplyMsg // 日志Apply的通道
2.2.3 Request Vote 相关定义
这个主要根据论文内容来,没有什么特别。需要注意的一点就是首字母得大写,否则会报错。
type RequestVoteArgs struct {
Term int // 候选人的term
CandidatedId int // 候选人的ID
LastLogIndex int // 候选人日志中最后一条的序号
LastLogTerm int // 候选人日志中最后一条的term
}
type RequestVoteReply struct {
Term int // 当前的term,用于使候选人更新状态
VoteGranted bool // 若为真,则表示候选人接受了投票
}
2.2.4 Append Entries 相关定义
这个也主要根据论文内容来,但是为了实现的方便,在AppendEntriesReply
中增加了一个NextIndex
的字段,直接标识若向某节点日志追加失败后,该节点的nextIndex
应该被置为何值。
type AppendEntriesArgs struct {
Term int // 领导者的term
LeaderId int // 领导者的ID,
PrevLogIndex int // 在append新log entry前的log index
PrevLogTerm int // 在append新log entry前的log index下的term
Entries []LogEntry // 要append log entries
LeaderCommit int // 领导者的commitIndex
}
type AppendEntriesReply struct {
Term int //
Success bool