文章目录
一致性
分布式系统最核心问题:维持多个节点副本的一致性。一致性协议通常基于replicated state machines,即所有结点都从同一个state出发,都经过同样的一些操作序列(log),最后到达同样的state。
- 状态机:当我们说一致性的时候,实际就是在说要保证这个状态机的一致性。状态机会从log里面取出所有的命令,然后执行一遍,得到的结果就是我们对外提供的保证了一致性的数据
- Log: 保存了所有修改记录
- 一致性模块: 一致性模块算法就是用来保证写入的log的命令的一致性,这也是raft算法核心内容
一致性算法的特性
- safety: 在非拜占庭条件下不会返回不正确的结果(网络延迟、分区、丢包、重复、顺序重排)
- available:前提大多数节点存活
- do not depend on timing to ensure the consistency of the logs
- a command can complete as soon as a majority of the cluster has responded to a single round of remote procedure calls
Raft状态和状态的转化
Raft采用问题分解法和状态简化法对一致性算法进行了简化,每个Raft实例会在以下三种状态下进行转化:
- Leader:所有请求的处理者,Leader副本接受client的更新请求,本地处理后再同步至多个其他副本;
- Follower:请求的被动更新者,从Leader接受更新请求,然后写入本地日志文件
- Candidate:如果Follower副本在一段时间内没有收到Leader副本的心跳,则判断Leader可能已经故障,此时启动选主过程,此时副本会变成Candidate状态,直到选主结束。
每种状态都可以用一个统一的Raft实例表示,用state属性来标识当前Raft所在的状态,按照论文中属性来定义Raft实例:
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[]
// Your data here (2A, 2B, 2C).
// Look at the paper's Figure 2 for a description of what
// state a Raft server must maintain.
currentTerm int //2A
votedFor int //2A
electionTimer *time.Timer //2A
heartbeatTimer *time.Timer //2A
state NodeState //2A
logs []LogEntry //2B
commitIndex int //2B
lastApplied int //2B
nextIndex []int //2B
matchIndex []int //2B
applyCh chan ApplyMsg //2B
}
Raft状态的转化过程
什么时候转化为Candidate
- follower -> candidate: follower的选举超时时间(electionTimer)到
- candidate -> candidate: candidate在选举时间内没有赢得选举并且也没有收到来自新leader的心跳
转化为candidate要开始进行选举。
什么时候转化为Leader
- candiadte -> leader: candidate 在获得majority选票时转化为leader
转化为leader后:初始化nextIndex[], 停止选举超时定时器、向其他server发送心跳、发送完心跳后也要重置心跳定时器(heartbeatTimer)
什么时候转化为Follower
- leader -> follower
- candidate -> follower
无论对于leader还是candidate转化为follower的前提就是在RequestVoteRPC和AppendEntriesRPC的request或者response中的term 大于 currentTerm.
转化为follower后心跳定时器停止(对于原来是leader的情况)、重置选举定时器、投票值voteFor清空
根据上述介绍可以定义状态转化方法convertTo, 完成状态转化后的操作,并在相应的情况下进行convertTo的调用
func (rf *Raft) convertTo(state NodeState) {
if rf.state == state {
return
}
DPrintf("Term %d: server %d convert from %v to %v\n",
rf.currentTerm, rf.me, rf.state, state)
// update state
rf.state = state
switch state {
case Follower:
rf.heartbeatTimer.Stop() //if native server is leader, stop heartbeat
rf.electionTimer.Reset(randomTime(ElectionTimeoutLower, ElectionTimeoutHigher))
rf.votedFor = -1 //clear votedFor
case Candidate:
rf.startElection()
case Leader:
for i := range rf.nextIndex {
rf.nextIndex[i] = len(rf.logs)
}
rf.electionTimer.Stop()
rf.broadcastHeartbeat()
rf.heartbeatTimer.Reset(HeartbeatInterval)
}
}
两个定时器
选举定时器(electionTimer), 当出现以下四种情况需要重置定时器:
- candidate或者leader转化为follower
- candidate或者follower收到来自leader的心跳
- candidate开始进行选举
- 投票给除自身以外的candidate
心跳定时器(heartbeatTimer), 当leader发送完心跳后重置
Raft实例初始化
主要完成Raft实例属性的初始化,并kick off 一个 goroutine 来处理 timer 相关的事件,注意加锁。有几个点需要注意:
- 所有的Raft实例都初始化为Follower
- logs[]的index要从1开始
rf.logs = make([]LogEntry, 1)
: logs 从第 1 个开始,这样第 0 个必然是所有节点都相同的。在发现log不一致时,nextIndex减一并在重试时不会越界。 - nextIndex[]初始化为len(logs)
- 采用select监听两个定时器,定时时间到完成相应的操作,并注意在读取state时加锁
func Make(peers []*labrpc.ClientEnd, me int,
persister *Persister, applyCh chan ApplyMsg) *Raft {
rf := &Raft{
}
rf.peers = peers
rf.persister = persister
rf.me = me
//2A
rf.currentTerm = 0
rf.votedFor = -1
rf.electionTimer = time.NewTimer(randomTime(ElectionTimeoutLower, ElectionTimeoutHigher))
rf.heartbeatTimer = time.NewTimer(HeartbeatInterval)
rf.state = Follower
//2B
rf.applyCh = applyCh
rf.commitIndex = 0
rf.lastApplied = 0
rf.logs = make([]LogEntry, 1)
rf.matchIndex = make([]int, len(rf.peers))
rf.nextIndex = make([]int, len(rf.peers))
for i := range rf.nextIndex {
rf.nextIndex[i] = len(rf.logs)
}
//2c persist
// initialize from state persisted before a crash
//rf.readPersist(persister.ReadRaftState())
rf.mu.Lock()
rf.readPersist(persister.ReadRaftState())
rf.mu.Unlock()
go func(node *Raft) {
for {
select {
case <-rf.electionTimer.C:
rf.mu.Lock()
if rf.state == Follower {
//there are two situations: Follower and Candidate
rf.convertTo(Candidate)
} else {
rf.startElection()
}
rf.mu.<