raft将服务器分成三种状态:
- leader
- follower
- candidate
将时间分割成任意长度的任期(term):
raft中服务器节点之间使用RPC进行通信,并且Raft只有两种主要的RPC: - RequestVote RPC(请求投票):由candidate在选举期间发起
- AppendEntries RPC(追加条目):由leader发起,用来复制日志和提供一种心跳机制
leader election
//由candiate发起
type RequestVoteArgs struct{
term int //自己当前的任期号
candidateId int //自己的ID,
//用来进行一致性检查
lastLogIndex int //自己最后一个日志号
lastLogTerm int //自己最后一个日志的任期号
}
//由follower回复candidate
type RequestVotereply struct{
term int //自己当前任期号
voteGrantedbool bool //自己是否投票给了这个candidate
}
心跳机制:如果存在leader,那么它就会周期性地向所有follower发送心跳,来维持自己的地位。如果follower一段时间没有收到心跳,那么他就会认为系统中没有可用的leader了,然后开始进行选举。
在启动raft时,会创建一个线程来监控心跳,超时则开启选举过程:
func (rf *Raft) electionMonitor(){
//使用for进行循环监测
for {
/*TODO*/
检查选举超时时间是否超时
生成随机选举超时时间
如:200~350ms
/*TODO*/
如果超时,并且当前为follower状态
则转变为candidate状态
/*TODO*/
如果已经是candidate状态,并且超时则开启下一次选举
1、发起新任期
2、投给自己
3、初始化RequestVoteArgs
然后并发RPC请求投票
4、使用channel接收RequestVoteReply
5、使用for循环持续统计投票数与应答数,并记录最大任期号
当全部应答或得到大多数投票后跳出循环
6、在接收投票期间,如果状态改变
不再是candidate则放弃本次投票结果
7、发现更高的任期号,则转变为follower
8、获得了大多数选票,成为leader
并立刻发送心跳确定地位
}
}
当candidate发起投票请求后,其他follower将需要进行处理:
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVotereply){
/*TODO*/
candidate的任期没有我大,拒绝投票
/*TODO*/
candidate的任期更大,则无论处于什么状态都转化为follower
/*TODO*/
每个任期只能投票给一个人
投票条件:candidate的日志必须比我的新
1、candidate的lastLogTerm大于我的lastLogTerm
2、lastLogTerm相同,则candidate的lastLogIndex大于等于我的lastLogTerm
投票成功,则重置选举超时时间
}
heartbeat
当candidate当选leader后,需要持续发送一定时间间隔的心跳包以维持自己的地位,因此,需要一个线程来监测发送时机:
func (rf *Raft) appendEntriesMonitor(){
/*TODO*/
只有leader才能向外广播
/*TODO*/
选择广播间隔
太长,容易造成选举时间超时
太短,占用大量RPC影响性能
例如选择:间隔100ms
判断距离上次广播的时间是否大于100ms
/*TODO*/
是,则立刻并发RPC向所有其他follower发送心跳包
通过AppendEntriesReply来判断自身状态是否应该改变
如果自身任期小于其他某个follower的任期,则自身放弃leader地位,变为follower
}
当leader向follower发送心跳包后,follower进行处理:
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply){
/*TODO*/
自身的任期号大于leader的任期号,则返回更大的任期号
/*TODO*/
自身的任期号小于leader的任期号,则无论出于何种状态,变为follower,即承认leader地位
/*TODO*/
心跳发送成功,刷新选举超时时间
}
注意锁的使用:
RPC期间释放锁,否则会引起死锁