Raft 共识算法4-选举限制
英原论文地址: https://raft.github.io/raft.pdfEtcd Assistant 是一款 etcd 可视化管理软件,便捷高效地操作您的 etcd 集群;支持多种键的视图;管理租约、用户、角色和权限。
前面的部分描述了 Raft 如何选举领导者和复制日志条目。 然而,到目前为止所描述的机制还不足以确保每个状态机以相同的顺序执行完全相同的命令。 例如,当领导者提交多个日志条目时,追随者可能不可用,然后它可以被选为领导者并用新的条目覆盖这些条目; 因此,不同的状态机可能会执行不同的命令序列。
本节通过添加对哪些服务器可以被选为领导者的限制来完成 Raft 算法。 该限制可确保任何给定任期的领导者都包含之前任期已提交的所有条目(@fig3 中的领导者完整性(Leader Completeness)属性)。 考虑到选举限制,然后我们使提交规则更加精确。 最后,我们展示了领导者完整性的证明草图,并展示了它如何保证复制状态机的正确行为。
选举限制
在任何基于领导者的共识算法中,领导者最终必须存储所有已提交的日志条目。 在一些共识算法中,例如 Viewstamped Replication,即使它最初不包含所有已提交的条目,也可以选出一个领导者。 这些算法包含额外的机制来识别丢失的条目并将它们传输给新的领导者,无论是在选举过程中还是之后不久。 不幸的是,这会导致相当多的额外机制和复杂性。
Raft 使用了一种更简单的方法,它保证从选举的那一刻起,每个新领导者都会出现以前任期的所有已提交条目,而无需将这些条目传输给领导者。 这意味着日志条目仅沿一个方向流动,从领导者到追随者,而领导者永远不会覆盖其日志中的现有条目。没有包含所有己提交日志条目的候选者成为不了领导者 。
一个时间序列显示了为什么领导者不能使用较早任期的日志条目来确定提交。在(a)中,S1是领导者,并部分复制了索引2的日志条目。在(b)中,S1崩溃了;S5凭借S3、S4和它自己的投票当选为第三任期的领导者,并接受了日志索引2的不同条目。在(c)中,S5崩溃了;S1重新启动,被选为领导者,并继续复制。在这一点上,任期2的日志条目已经在大多数服务器上复制,但它实际上并不能被认为是已提交的(即该日志条目不能被安全地应用到状态机)。因为如果S1像(d)那样崩溃,S5可以被选为领导者(有S2、S3和S4的投票),并用它自己的任期3的条目覆盖该条目。然而,如果S1在崩溃前在大多数服务器上复制了其当前任期的条目,如(e),之后该条目被提交了(S5不能赢得选举)。在这一点上,日志中所有前面的条目也被提交。
Raft使用投票过程来防止候选者赢得选举,除非其日志包含所有已提交的条目。候选者必须与集群中的大多数跟随者联系才能当选,这意味着每个已提交的日志条目必须至少存在于其中一个服务器中。如果候选者的日志至少和该多数跟随者中的任何其他日志一样是最新的(这里的 "最新 "在下面有精确的定义),那么它将包含所有已提交的条目。RequestVote RPC实现了这一限制:RPC包括关于候选人日志的信息,如果投票人自己的日志比候选人的日志更新时,则拒绝投票。
Raft 通过比较日志中最后条目的索引和任期来确定两个日志中哪一个是最新的。 如果日志的最后条目具有不同的任期,则具有较晚任期的日志是最新的。 如果日志以相同的任期结尾,则具有更大索引的那个条目是最新的。
提交以前任期的日志条目
如第 5.3 节所述,领导者知道一旦该条目存储在大多数服务器上,其当前任期的条目就会被认为是已提交的。 如果领导者在此时崩溃,未来的领导者将尝试完成复制条目。 但是,领导者不能立即断定上一个任期的条目一旦存储在大多数服务器上就已提交。 @fig8 说明了一种情况,其中旧日志条目存储在大多数服务器上,但仍可能被未来的领导者覆盖。
为了消除 @fig8 中的问题,Raft 限制只能通过判断大多数的方式提交当前任期的日志条目,进而对之前的日志条目间接提交(也就是说,对之前任期的日志条目不是通过通过判断大多数的方式来提交,而是通过提交当前任期的日志条目来间接提交); 一旦以这种方式提交了当前任期的条目,则由于日志匹配(Log Matching)属性,所有先前的条目都将间接提交。 在某些情况下,领导者可以安全地断定一个较旧的日志条目已提交(例如,如果该条目存储在每个服务器上),但 Raft 为简单起见采用了更保守的方法。
Raft 在提交规则中引入了这种额外的复杂性,因为当领导者从以前的任期复制条目时,日志条目会保留其原始任期号。 在其他共识算法中,如果新领导者重新复制先前“任期”中的条目,则它必须使用新的“任期号”进行复制。 Raft 的方法使得对日志条目的推理变得更容易,因为它们随着时间的推移和跨日志保持相同的任期编号。 此外,与其他算法相比,Raft 中的新领导人发送的前任期日志条目更少(其他算法必须发送冗余日志条目以重新编号,然后才能提交)。
安全论证
给定完整的 Raft 算法,我们现在可以更准确地论证领导者完整
性(Leader Completeness)属性成立(该论证基于安全证明;参见第 9.2 节)。 我们假设领导者完整
性(Leader Completeness)不成立,然后我们证明一个矛盾。 假设任期 T 的领导者 (leader#sub[T]) 从其任期提交了一个日志条目 $a$,但该日志条目未被未来某个任期的领导者存储。 考虑领导者(leader#sub[U])不存储该日志条目的最小任期 U(U > T)。
如果 S1(任期 T 的领导者)从其任期提交了一个新的日志条目,并且 S5 被选为后来的任期 U 的领导者,那么必须至少有一个服务器(S3)接受了日志条目并且也投票给了 S5。
- 提交的条目 $a$ 在选举时一定不在 leader#sub[U] 的日志中(领导者永远不会删除或覆盖条目)。
- leader#sub[T] 在大多数跟随者上复制了该条目 $a$,而 leader#sub[U] 收到了来自大多数跟随者的投票。 因此,至少有一个服务器(“投票者”)既接受了来自 leader#sub[T] 的条目 $a$,又投票给了 leader#sub[U],如 @fig9 所示。投票者是达成矛盾的关键。
- 投票者必须在投票给 leader#sub[U] 之前接受来自 leader#sub[T] 的提交条目 $a$; 否则它会拒绝来自 leader#sub[T] 的 AppendEntries RPC 请求(它的当前任期会高于 T)。
- 投票者在投票给 leader#sub[U] 时仍然存储该条目 $a$,因为领导者永远不会删除条目,而追随者只有在与领导者发生冲突时才会删除条目。
- 投票者将其投票给了 leader#sub[U],因此 leader#sub[U] 的日志必须至少与投票者的日志一样最新。 这导致了下面两个矛盾之一。
- 首先,如果投票者和 leader#sub[U] 共享相同的最后一个日志任期,那么 leader#sub[U] 的日志一定至少和投票者的一样新,所以它的日志包含了投票者日志中的每个条目。 这是一个矛盾,因为投票者包含已提交的条目 $a$,而 leader#sub[U] 被假定为不包含 $a$。
- 否则,leader#sub[U] 的最后一个日志条目一定比投票者的大。 此外,它比 T 大,因为投票者的最后一个日志任期至少是 T(它包含来自任期 T 的已提交条目)。 创建 leader#sub[U] 的最后一个日志条目的较早领导者必须在其日志中包含已提交的条目(根据假设)。 那么根据日志匹配属性(Log Matching),leader#sub[U]的日志也必须包含已提交的条目 $a$,这是矛盾的。
- 这就完成了矛盾。 因此,所有大于 T 的任期的领导者必须包含任期 T 中在任期 T 中提交的所有条目。
- 日志匹配属性(Log Matching)保证未来的领导者也将包含间接提交的条目,例如 @fig8 (d) 中的索引 2。
鉴于证领导者完整性(Leader Completeness Property)成立,我们可以证明 @fig3 中的状态机安全属性,该属性表明如果服务器已将给定索引处的日志条目应用于其状态机,则相同的索引处不会有另一个不同的日志条目应用于状态机。当服务器将日志条目应用于其状态机时,其日志必须与通过该条目向上的领导者日志相同,并且必须提交该条目。 现在考虑任何服务器应用给定日志索引的最低期限;日志完整性属性保证所有更高任期的领导者将存储相同的日志条目,因此在以后的任期应用索引的服务器将应用相同的值。 因此,状态机安全属性成立。
最后,Raft 要求服务器按日志索引顺序应用条目。 结合状态机安全属性,这意味着所有服务器将以相同的顺序将完全相同的一组日志条目应用到它们的状态机。