Safety
Safety章节主要讨论了,raft如何保证每个节点的状态机,以相同的顺序执行相同的命令。
上面的章节,关于raft的特性,已经讨论了Election Safety, Log Matching, Leader Append-only。接下来,我们首先看看Leader Completeness。
Leader Completeness
首先,再次回顾对Leader Completeness的定义:
如果一个日志entry在给定的任期已经被提交,
那么这个日志entry必然会出现在所有任期大于这个给定任期的leaders的日志中
关于Leader Completeness,会有两个约束(规则)
第一个约束
在选举的时候会有一个选举约束:
投票者不会给没有自己日志新的Candidate投选票。
Leader在RequestVote时,会携带自己最后一条entry的index和term,投票者会将这两个参数和自己的对比。 关于最新的定义:
1) 最后一个entry的term不同时,term大的为新;
2)最后一个entry的term相同时,index大的为新;
第二个约束
成为Leader后,Commit日志时:
Leader不会Commit当前任期之前任期的日志
(a)阶段 S1 为Leader,复制index = 2, term = 2 日志到S2, 其他节点还没有复制成功; (b)阶段S1发生崩溃,触发Leader选举: term变成3,S5变成新的Leader。为什么S5可以被推选成新的Leader? S3、S4可以投票给S5,S1、S2由于在index = 2 处的日志比S5新,不会投票给S5,但S5仍然可以有3票当选。 S5当选后,收到来自于客户端的请求,此时会在index = 2, term = 3 处追加日志,当时还没有来得及复制日志,发生了崩溃; (c)由于S5的崩溃,触发选举: 同理term变成4,S1可以获取S2、S3、S4的选票而当选; S1在index = 3, term=4 处追加日志,开始日志复制; S1将index = 2, term = 2 的日志复制到S3,日志条目被复制到大多数的机器上,该日志条目可以被提交(也就是可以被执行),此时S1崩溃; (d)由于S1的崩溃,触发选举: term变成5,S5可以通过S2、S3、S4的选票而当选; S5当选后,会覆盖其他server在index = 2 的日志,这样错误就发生了。 为什么说错误发生了? index = 2, term = 3 的日志覆盖了大部分机器上存在的index = 2, term = 2 的entry,也就是S1执行过的命令被覆盖。 对于c->d过程中可能产生的错误,raft的做法是: 对于c中index =2, term = 2 的日志,因为当前的term = 4 , 对于不属于当前term的entry,raft不会根据是否复制到半数以上节点 原则来决定是否提交,只有对属于当前term的entry,才会采用这一原则来决定是否提交。 也就是(e)中的,index = 2, term = 2 的日志被复制到多数节点后,这个日志还不会提交;只有index = 3, term = 4 可以被提交时,index = 2 的日志才可以被提交。这样就不会发生S1离线,日志被覆盖的情况。
问题一:
过程(b)中,S2是否会给S5投票? 不会(Election restriction)。 S5的RequestVote RPC为:
RequestVoteArg {
Term = 3,
CandidateId = me, // me指自身的节点id
LastLogIndex = 1,
LastLogTerm = 1,
}
S2收到S5的请求后,会做如下check:
//如下是S2最新的日志,这个日志可能是一个committed日志,也可能是一个没有committed的日志
lastIndex = 2;
lastTerm = 2;
// Election restriction
if arg.LastLogTerm < lastTerm {
return false;
}
if arg.LastLogTerm == lastTerm && arg.LastLogIndex < lastIndex {
return false;
}
基于以上准则,无论S2的第二条日志是否committed,都不会投票给S5
问题二:
index = 3, term = 4 可以被提交后,index = 2 的日志才可以被提交是什么意思?,index = 2, term = 2 是如何被应用的状态机的?
在(e)中S1重新被选举为Leader后:
nextIndex[i] = 3; // 即日志长度的下一个index;
当新的日志条目index = 3, term = 4 到达后,开始复制日志,以向S3复制日志为例:
AppendEntryArg {
term = 4,
LeaderId = me,
PrevLogIndex = 2,
PrevLogTerm = 2,
entry = log[3],
}
即会在参数中,携带自身上次同步日志的位置。 在S3侧,自身的日志信息为:
lastIndex = 1;
if lastIndex < arg.PrevLogIndex {
reply.ConflictIndex = lastIndex + 1;
return fasle;
}
即会检测到日志的不一致(一致性检查, Log Matching) S1拿到了日志不一致的问题,会重新Append Entries,且会将从冲突开始的日志,都发送给S3
AppendEntryArg {
term = 4,
LeaderId = me,
PrevLogIndex = 1,
PrevLogTerm = 1,
entry = log[2], log[3],
}
S3收到了新的AppenEntries后,此时一致性检查通过,会append 2条entry 到自己的日志中,且直接更新自己的commitIndex = 3 , 此时自己的lastApplied = 1 ,因此会apply两条entry。
|