In Search of an Understandable Consensus Algorithm
Abstract
Raft 非常好理解,结果等同于Paxos, 效率和Paxos 相同,为构建实际系统提供更好的基础。为了更好理解,分离关键元素:leader election, Log replication, 安全,执行更强的一致性,以减少状态数量R。aft 还包括一个改变集群成员的新机制,它使用重叠的多数来保证安全.
1. Introduction
共识算法使得一组机器协同工作,即使某些成员的故障,也能正常工作,在构建可靠的大型软件系统方面发挥着关键作用。 Paxos在过去十年中主导了共识算法; 大多数共识算法的实现都基于 Paxos 或受其影响,Paxos 已成为用于教授学生共识的主要工具。
但Paxos 很难理解,此外,它的实际系统需要架构做出复杂的更改,。结果,Paxos 的开发和学习者都遇到困难。这篇文章的目标是:更加容易理解的共识算法,对于学习、构建实际系统有着很重要的意义。结果就是Raft被研发出来了。Raft 利用解耦提升理解性。(Raft 分为leader election, log replication和安全)同时减少状态空间(相对于Paxos,减少了不确定性程度和server之间不一致的方式)。
Raft 与现有的共识算法很相似,但是有几个不同的feature.
- strong leader: Raft 相对于其余共识算法那,使用强领导形式。例如:log 只从leader 发往其余server,简化了日志分发。
- Leader election: Raft 使用随机事件来竞选leader, 原有心跳中添加了少量的机制,但是能简单、快速解决冲突。
- Membership changes: 集群中server 修改时,使用了新的联合共识方法, 两种不同的配置在变更期间重叠,允许集群在配置变更的时候,持续正常运行。
从学习、工程实现上,raft 算法优于Paxos和其他共识算法。 易于理解,完全面向工程实现需要; 被多个公司使用,安全性已经被证明。Section2介绍了复制状态机,Section3 介绍了Paxos的缺点, Section4 介绍为了获取可理解性的方法,Section 5 ~ 8 接受了Raft, section9 评估了Raft, Section10 接受相关工作
2. Replicated state machine
共识算法常出现在复制状态机,一组服务器上即使server宕机也会按照相同状态运行。复制状态机用于解决分布式一系列问题,如有单个leader的系统,如GFS, HDFS 和RAM Cloud,大多使用复制状态机进行leader 选举,配置信息存储等。
复制状态机通常使用复制日志来实现,如图 1 所示。每个服务器存储一个包含一系列命令的日志,其状态机按顺序执行这些命令。每个日志包含相同顺序的相同命令,因此每个状态机处理相同的命令序列。由于状态机是确定性的,每个状态机计算状态相同和相同的输出序列。
保持复制日志的一致性是共识算法的工作。服务器上的共识模块接收来自客户端的命令并将它们添加到其日志中。它与其他服务器上的共识模块进行通信,以确保每个日志最终以相同的顺序包含相同的请求,即使某些服务器出现故障。一旦命令被正确复制,每个服务器的状态机按日志顺序处理它们,并将输出返回给客户端。结果,这些服务器似乎形成了一个单一的、高度可靠的状态机。
使用公式算法的实际系统有以下特点:
-
它们确保在所有非拜占庭条件下的安全性(永远不会返回错误的结果),包括网络延迟、分区和数据包丢失、复制和重新排序。
-
只要大多数服务器都可以运行并且可以相互通信并与客户端通信,它们就可以正常工作(可用)。因此,一个典型的五台服务器集群可以容忍任何两台服务器的故障。假设服务器因停止而失败;它们稍后可能会从稳定存储上的状态恢复并重新加入集群。
-
他们不依赖时间来确保日志的一致性,错误的时钟和极端的消息延迟在最坏的情况下会导致可用性问题。
-
在常见的情况下,只要集群的大部分响应了一轮远程过程调用,命令就可以完成。少数慢速服务器不需要影响整体系统性能
3. Paxox 出了什么问题
在过去的十年中,Leslie Lamport 的 Paxos 协议占据主导地位。Paxos 首先定义了一种能够就单个决策达成一致的协议,例如单复制的日志条目。我们将此子集称为单法令 Paxos。然后 Paxos 结合了该协议的多个实例,以促进一系列决策。 Paxos 确保了安全性和活跃性,并且它支持集群成员的变化。它的正确性已经被证明,并且在正常情况下是有效的。
不幸的是,Paxos 有两个明显的缺点。第一个缺点是 Paxos 非常难以理解。完整的解释是出了名的不透明。付出巨大的努力后,很少有人能够成功理解。因此,已经有几次尝试用更简单的术语来解释 Paxos 。这些解释关注与在单一法令的子集上,但它们仍然具有挑战性。在 NSDI 2012 对与会者的非正式调查中,我们发现很少有人对 Paxos 感到满意,即使是经验丰富的研究人员也是如此。我们自己在 Paxos 上苦苦挣扎;直到阅读了几个简化的解释并设计了我们自己的替代协议之后,我们才能够理解完整的协议,这个过程花了将近一年的时间。
我们假设 Paxos 的不透明性源于它选择单一法令子集作为其基础。单法令 Paxos 很密集,分为两个阶段,没有简单直观的解释,无法独立理解。正因为如此,很难对单一法令协议的工作原理产生直觉。多 Paxos 的组合规则显着增加了额外的复杂性和微妙性。我们相信,就多个决策(即日志而不是单个条目)达成共识的整体问题可以分解成更明显和直观的方式。
Paxos 的第二个问题是它没有为构建实际系统提供良好的基础。一个原因是没有广泛认可的多 Paxos 算法。 Lamport 的描述主要是关于单一法令 Paxos;他勾勒了多 Paxos 的可能方法,但缺少许多细节。已经有几次尝试充实和优化 Paxos,但这些都不相同而且和Lamport’s的框架也有所不同。
此外,Paxos 架构对于构建实际系统来说是一种糟糕的架构。这是单一法令分解的另一个结果。例如,单独选择一组日志条目,然后将它们融合到一个顺序日志中。这只会增加复杂性,并没有什么好处。围绕日志设计一个系统更简单、更有效,其中新条目以受约束的顺序顺序附加。另一个问题是 Paxos 在其核心使用对称的点对点方法(尽管它最终提出了一种弱领导形式作为性能优化)。这在一个只会做出一个决定的简化世界中是有意义的,但很少有实际系统使用这种方法。如果必须做出一系列决策,首先选举一个领导者,然后让领导者协调决策会更简单、更快捷。
因此,实际系统与Paxos几乎没有相似之处。每个实现都从 Paxos 开始,发现实现它的困难,然后开发出截然不同的架构。这是耗时且容易出错的,而且理解 Paxos 的困难加剧了这个问题。 Paxos 的公式对于证明其正确性的定理可能是一个很好的公式,但实际的开发与 Paxos 是如此不同,以至于证明没有什么价值。 Chubby 开发者的发表了以下典型的评论:
Paxos 算法的描述与现实世界系统的需求之间存在很大差距。 . . .最终系统将基于未经验证的协议.
由于这些问题,我们得出结论,Paxos 没有为系统构建或学习提供良好的基础。考虑到共识在大规模软件系统中的重要性,我们决定看看我们是否可以设计一种性能比 Paxos 更好的替代共识算法。 而Raft 就是结果。
4. 为可理解性而设计
我们在设计 Raft 时有几个目标:它必须为系统构建提供完整且实用的基础,从而显着减少开发人员所需的设计工作量;它必须在所有条件下都是安全的,并且在典型的操作条件下可用;并且对于常见的操作必须是高效的。但我们最重要的目标——也是最困难的挑战——是可理解性。必须能够轻松地理解算法。此外,必须有可能开发关于算法的直觉,以便系统开发者可以在现实系统中进行扩展。
使用两种方法让Raft 容易理解:分解问题,将领导者选举、日志复制、安全性和成员资格更改分开;第二种减少状态数量:让系统连贯、消除不确定性,日志不允许有漏洞,Raft 限制了日志不一致的方式。 某些情况下,不确定性实际上提高了可理解性,如:随机方法引入了非确定性,但它们倾向于通过以相同的方式处理所有可能的选择来减少状态空间(“选择任何一个;没关系”)
5. Raft 一致性算法
Raft 是一种用于管理第 2 节中描述的形式的复制日志的算法。图 2 以精简形式总结了该算法以供参考,图 3 列出了该算法的关键属性;这些图的元素将在本节的其余部分进行分段讨论。
Figure2:一致性算法的凝练版本(不包括member变更, 日志压缩)
Raft 通过首先选举一个leader来实现共识,然后让领导者完全负责管理复制的日志。领导者接受来自client的log条目,将它们复制到其他server上,并告诉server何时可以安全地将日志条目应用于其状态机。拥有领导者可以简化复制日志的管理。例如,leader可以在不咨询其他服务器的情况下决定在日志中放置新条目的位置,并且数据以简单的方式从leader流向其他服务器。leader可能会故障或与其他server断开连接,在这种情况下会选出新的leader。
-
选举安全:在给定任期内最多可以选举一个领导者。 §5.2
-
leader仅追加日志:领导者永远不会覆盖或删除其日志中的条目;它只附加新条目。 §5.3
-
日志匹配:如果两个日志包含具有相同的index和term,则在通过给定索引的所有条目中,日志都是相同的。 §5.3
-
领导者完整性:如果在给定期限内提交了日志条目,则该条目将出现在所有较高编号期限的领导者日志中。 §5.4
-
状态机安全:如果服务器已将给定索引处的日志条目应用到其状态机,则其他服务器不会在同一索引应用日志条目 §5.4.3
Figure3: raft始终保证以上原则
Raft 将共识问题分解为三个相对独立的子问题:
- leader election:当现有领导者故障时必须选择新领导者
- log replication: 领导者必须接受来自client的log entry,在集群中复制它们,强制其他日志同意它自己的
- safety: Raft 的关键安全属性是图 3 中的状态机安全属性:如果任何服务器已将特定日志条目应用到其状态机,则没有其他服务器可以为相同的日志索引应用不同的命令。 5.4 节描述了 Raft 如何确保这个属性;该解决方案涉及对选举机制的额外限制
5.1 Raft 基础
一个 Raft 集群包含多个服务器;5是一个典型的数字,它允许系统容忍两个server故障。在任何时间,每个服务器都处于以下三种状态之一:
- leader
- follower
- candidate
一般只有一个leader,所有其他服务器都是follewer。追随者是被动的:他们不会自己发出请求,而只是响应领导者和候选人的请求。领导者处理所有client请求(如果client联系follwer,则follewer将其重定向到lieader)。候选者状态,用于选举一个新的领导者,如第 5.2 节所述。Figure 4显示了状态及其转换。
Figure4: Server的状态及其转化
Raft 将时间划分为任意长度的term(任期),如图 5 所示。term用连续的整数编号。每个term都以选举开始,其中一个或多个候选人尝试成为第 5.2 节所述的领导者。 赢取选举的candidate在下一个任期成为leader。在某些情况下,选举会导致平分选票。在这种情况下,term将在没有领导者的情况下结束;新任期(新选举)开始,保证最多一个leader。
不同的server可能会在不同的时间察觉到term的变化,并且在某些情况下,server可能不会觉察到整个选举甚至整个任期。term在 Raft 中充当逻辑时钟,它们允许服务器检测过时的信息,例如过时的领导者。每个服务器存储一个当前term编号,该编号随时间单调增加。每当服务器通信时都会交换当前term;如果一个服务器的当前任期小于另一个,那么它将其当前任期更新为更大的值。如果候选人或领导者发现其term已过时,它会立即恢复到follower状态。如果服务器接收到带有过期term的请求,它会拒绝该请求。
Raft 服务器使用远程过程调用(RPC)进行通信,基本的共识算法只需要两种类型的 RPC。 RequestVote RPC 由候选人在选举期间发起(第 5.2 节),而 AppendEntries RPC 由leader发起以复制日志条目并提供一种心跳形式(第 5.3 节)。第 7 节添加了第三个 RPC,用于在服务器之间传输快照。如果服务器没有及时收到响应,服务器会重试 RPC。而且RPC 调用是并行的用来获得最佳性能。
5.2 Leader 选举
Raft 使用心跳机制来触发领导者选举。当服务器启动时,它们以follower的身份开始, 指导收到来自leader 或candidate的RPC。领导者定期向所有follower发送心跳(AppendEntries RPC,不携带日志条目)以维护他们的权限。如果一个follewer在称为选举超时的一段时间内没有收到任何通信,那么它会假设没有leader, 并开始选举一个新的领导者。
要开始选举,追随者会增加其当前任期并转换到候选状态。然后它为自己投票,并向集群中的每个其他服务器并行发出 RequestVote RPC。 这种状态直到发生以下三种情况之一:(a) 它赢得了选举,(b)另一个服务器将自己建立为领导者,© 一段时间过去了,没有赢家。 这些外
来分别在下面的段落中讨论。
A candidate 得到大多数server的选票后,赢得选举,每个服务器将在给定term内最多投票给一个候选人,先到先得(注意:第 5.4 节增加了对投票的额外限制)。多数规则确保至多一名候选人可以赢得特定任期的选举(图 3 中的 election safety)。 一旦赢得了选举,就成了新的leader,然后它将心跳消息发送到所有其他服务器以建立其权限并阻止新的选举。
在等待投票时,候选人可能会从另一个声称自己是leader的服务器收到 AppendEntries RPC。如果领导者的term(包含在RPC 调用中)与候选人的当前term一样大,那么候选人将leader识别为合法并返回follower状态。如果 RPC 中的term小于候选者的当前term,则候选者拒绝 RPC 并继续处于candidate状态。
第三种可能结果是candidate既没有赢也没有失败:如果许多同时成为候选人,则可以分裂投票,以便没有候选人获得多数。发生这种情况时,每个候选人将超时并通过增加其任期并启动另一轮 Request-Vote RPC 来开始新的选举。然而,如果没有额外的措施,平分投票可能会无限期地重复。、
Raft 使用随机选举超时来确保平分投票很少发生并且可以快速解决。为了首先防止平分投票,选举超时是从固定间隔(例如,150-300ms)中随机选择的。这分散了服务器,因此在大多数情况下,只有一个服务器会超时;它赢得了选举并在任何其他服务器超时之前发送心跳。相同的机制用于处理平分投票。每个候选人在选举开始时重新设置。其随机选举超时,并在开始下一次选举之前等待该超时过去;这减少了在新选举中再次分裂投票的可能性。Section 9.3 表明,这种方法可以快速选举领导者。
选举设计备选方案之间的选择考虑到了可理解性。最初我们计划使用排名系统:为每个候选人分配一个唯一的排名,用于在竞争候选人之间进行选择。如果一个候选人发现另一个更高级别的候选人,它将返回跟随者状态,以便更高级别的候选人更容易赢得下一次选举。我们发现这种方法在可用性方面产生了微妙的问题(如果排名较高的服务器发生故障,排名较低的服务器可能需要超时并再次成为候选者,但如果它过早地这样做,它可能会重置选举领导者的进度)。我们对算法进行了多次调整,但每次调整后都会出现新的极端情况。最终我们得出结论,随机重试方法更易于理解。
5.3 Log replication
一旦选出了领导者,它就会开始为client请求提供服务。每个客户端请求都包含一个要由复制的状态机执行的命令。领导者将命令作为新条目附加到其日志中,然后向其他每个服务器并行发出 AppendEntries RPC 以复制该条目。当条目被安全复制后(如下所述),领导者将条目应用到其状态机并将执行结果返回给客户端。如果 follower 崩溃或运行缓慢,或者网络数据包丢失,leader 会无限期地重试 AppendEntries RPC(即使它已经响应了客户端),直到所有 follower 最终存储所有日志条目。
日志如图6所示。每个日志条目都存储一个状态机命令以及leader收到条目时的term。日志条目中的term编号用于检测日志之间的不一致,并确保图3 中的某些属性。每个日志条目还有一个整数索引, 用于标识其位置。
leader决定何时将日志条目应用于状态机是安全的;这样的条目称为已提交(committed)。 Raft 保证提交的条目是持久的,并且最终会所有的状态机执行。一旦创建entry leader的在大多数服务器上复制了日志term(例如,图6中的条目7),就会提交日志条目。也会提交领导者log中的先前所有entry,包括以前的leader创建的entry。 5.4 节讨论了在领导人变更后应用此规则时的一些微妙之处,并且还表明这种commit是安全的。leader追踪提交的最高索引,并将该索引包含在未来的 AppendEntries RPC(包含在心跳)中,以便其他服务器最终发现。一旦追随者得知一个日志条目已提交,它就会将该条目应用到其本地状态机(按日志顺序)。
我们设计了raft日志机制,让不同服务器上的log之间保持高水平的一致性。简化系统行为,更加可预测,同时确保安全。 RAFT维护以下适当关系,其中共同构成图3中的日志匹配属性:
- 如果不同日志中的两个条目具有相同的索引和term,则它们存储相同的命令。
- 如果不同日志中的两个条目具有相同的索引和term,则在所有前面的条目中日志都是相同的。
第一个属性来自: 在给定日志index和term中,leader最多创建一个entry, 而且log entry 永远不会在更改其位置。
第二个属性:AppendEntries中简单一致性检查保证。发送rpc时,leader 将前一个entry的索引和term包括中。如果follower没有找到具有相同索引和术语的日志中的条目,那么它拒绝了新条目。一致性检查充当归纳步骤:日志的初始空状态满足日志匹配属性,而一致性检查保证日志匹配属性。因此,每当AppendEntries成功返回时,leader都知道follower的log到最新的entry 完全相同。
在正常操作期间,leader和follower的日志保持一致,因此 AppendEntries 一致性检查永远不会失败。但是,leader 崩溃会导致日志不一致(旧的 leader 可能没有完全复制其日志中的所有entry)。这些不一致会在一系列领导者和追随者崩溃中加剧。图 7 说明了follwer的日志可能与新领导者的日志不同的方式。follower可能缺少在新leader上日志, 或者follower有leader上不存在的日志, 或者两种情况都有。缺少和无关entry可能跨越多个term。
在 Raft 中,领导者这样解决不一致问题,强制follower的复制自己的日志。这意味着跟随者日志中的冲突条目将被领导者日志中的条目覆盖。Section 5.4 节将表明,当再加上一个限制时,这是安全的。为了使follower的日志与自己的日志保持一致,领导者必须找到两个日志一致的最新日志条目,删除该点之后跟随者日志中的所有条目,并将该点之后的所有领导者的条目发送给跟随者。所有这些操作都是为了响应 AppendEntries RPC 执行的一致性检查而发生的。 leader为每个follower维护一个 nextIndex,这是领导者将发送给该随者的下一个日志条目的索引。当领导者首次掌权时,它会将所有 nextIndex 值初始化为其日志中最后一个值之后的索引(图 7 中的 11)。如果follower的日志与leader的日志不一致,则 AppendEntries 一致性检查将在下一个 AppendEntries RPC 中失败。拒绝后,领导者减少 nextIndex 并重试 AppendEntries RPC。最终 nextIndex 将达到leader和follower日志匹配的点。发生这种情况下AppendEntries 成功,这将删除跟随者日志中的任何冲突条目,并从日志中附加条目。 AppendEntries 操作成功,follower 的 log 就会与 leader 的一致。
优化协议,减少被拒绝的 AppendEntries RPC 的数量。例如,当拒绝 AppendEntries 请求时,follower包括冲突条目的term和它为该term存储的第一个索引。
有了这些信息,领导者可以减少 nextIndex 以绕过该术语中的所有冲突条目;每个冲突的term发送 AppendEntries RPC,而不是每个entry一个 RPC。但不确定这种优化是否需要,因为故障很少发生,而且不太可能有很多不一致的entry。
通过这种机制,leader在上台时无需采取任何特殊措施来恢复日志一致性。只需要正常运作,Append-Entries 一致性检查的失败时,日志会自动收敛 。leader永远不会覆盖或删除自己日志中的条目(图 3 中的领导者仅追加属性)。这种日志复制机制展示了第 2 节中描述的理想共识属性:只要大多数server正常运行,Raft 就可以接受、复制和应用新的日志entry;正常情况下,单轮 RPC 将新entry复制到集群中大部分机器;并且单个比较慢的follower不会影响性能。
5.4 安全性
目前的机制还不足以确保每个状态机以相同的顺序执行命令。例如,一个follower故障时,但是leader提交多个日志entry,然后这个followe可以被选举为leader, 用新的条目覆盖这些条目,所以不同的状态机可能会执行不同的命令序列
5.4.1 选举限制
在任何基于leader的共识算法中,领导者最终都必须存储所有提交的日志条目。在某些共识算法中,例如 Viewstamped Replication [22],即使最初不包含所有已提交的条目,也可以选举领导者, 但是这些算法包含额外的机制来识别丢失的条目并将它们传输给新的领导者,但会造成额外机制和复杂性。 Raft 用更简单的方法,保证所有之前提交的entry出现在新选的leader上,这意味着log entries只能从leader传往follower。
RAFT在投票过程来防止那些日志不包含所有commited的条目candidate获胜。为了获胜,一个candidate必须和cluster中大多数server通信,意味着这些server中至少有一台包含着所有已经committed entry的机器。
如果candidate的日志多数中server中的日志一样是最新的(“最新”在下面精确定义),那么它将保存所有committed的entry。 RequestVote RPC 有如下限值限制:RPC 包含有关候选人日志的信息,
如果投票者自己的日志比candidate的日志新,则投票者拒绝投票。
Raft 通过比较日志中最后一个entry的index和term来确定两个日志中哪个更新。term大日志是最新的,term相同,那么index大的更新。
5.4.2 提交之前term的entry
如第 5.3 节所述,一旦条目存储在大多数服务器上,领导者就知道其当前term内的entry已提交。如果领导者在提交条目之前崩溃,未来的leader将尝试完成复制条目。然而,leader不能通过自己在上一任期的entry存储在大多数服务器就断定这个entry已经提交了。图8展示了旧的日志(黄色的2)存于大多数server中,但是任然被新的leader覆盖了。
为了消除图 8 中的问题,Raft 从不通过计算副本来提交先前term的日志entry。来自当前leader 当前任期的entry才会通过计算副本来提交。一旦按照这样的方式提交了当前term的entry, 之前的entry 会根据日志匹配原则间接提交。 在某些情况下,leader可以安全地断定已提交较旧的日志条目(例如,如果该条目存储在每台服务器上),但 Raft 为简单,采用保守的方法。
Raft 在提交规则中产生了这种额外的复杂性,因为当领导者从之前的任期中复制条目时,日志条目会保留其原始任期号。在其他共识算法中,如果新的leader从先前的“term”中复制条目,它必须使用新的“term编号”。Raft 的方法让推理日志条目变得更容易,因为他们随着时间推移term不会改变。此外,与其他算法相比,Raft 中的新领导者发送的先前term的日志条目更少(其他算法必须通过发送冗余日志条目达到重新编号的目的,然后才能提交)
5.4.3 安全性论证
了解完整的 Raft 算法后,可以更准确地论证Leader Completeness Property(这个论证基于安全证明;参见第 9.2 节)。我们假设Leader Completeness Property不成立,那么我们证明一个矛盾。假设任期 T (leaderT) 的领导者提交了其任期内的一个日志条目,但未来某个任期的领导者没有存储该日志条目。考虑term U > T,其领导者 (leaderU) 不存储条目。
-
提交的entry 肯定不在Leader - U中的log, leader不会删除和覆盖entry.
-
leader-T 将entry 复制到绝大多数的server, leader-U获得大多数server的投票,至少有一台机器即获得了leader-T的entry, 同时也给leader-U投票了(图9), 这个是矛盾的关键
-
这个Voter 必须在给leader-U投票前接受了这个提交了的entry, 否则投票时Term数增加,会拒绝leader-T的appendEntries
-
投票者在投票给leaderU时仍然存储该条目,因为每个干预的领导者都包含该entry(根据假设),leader永远不会删除条目,follewer只会在与leader冲突时删除条目
-
voter投票给leaderU,因此leaderU的日志必须与voter的日志一样是最新的。这导致了两个矛盾之一
-
第一种情况,如果 voter 和 leaderU 共享相同的最后一个 log term,那么 leaderU 的 log 必须至少与 voter 的一样长,这是一个矛盾,因为投票者包含已提交的条目,而 leaderU 被假定为不包含.
-
否则, leaderU 的最后一个 log term 必须大于 voter 的。此外,它大于 T,因为投票者的最后一个日志项至少为 T(它包含来自 T 项的已提交条目)。创建leaderU的最后一个日志条目的早期领导者必须在其日志中包含已提交的条目(假设)。那么,通过Log Matching Property,leaderU的日志也必然包含committed entry,这是矛盾的。
-
这样就完成了矛盾论证。因此,所有大于 T 的term的leader必须包含在term T 中提交的所有条目。
-
log matching Property 保证未来leader囊括了间接提交的entry.
(9个步骤,用矛盾法证明 Leader Completeness Property)
最终,Raft 要求server 按照index 顺序执行log, 所有server 状态一定相同
5.5 Follower 和 candidate 故障
这两种情况很好处理,故障发生时,RequestVote and AppendEntries RPC 会失败。raft 会无限重发RPC 直到成功, Raft RPCs 是幂等的,所以不会造成故障。
5.6 时间和可用性
raft的安全性不是基于时间的,系统不能仅仅因为某些事件发生得比预期快或慢而产生不正确的结果。然而,可用性(系统及时响应客户的能力)必然取决于时间。 例如,如果消息交换所需时间超过服务器崩溃需要的时间,候选人将无法等待足够长的时间赢得选举;没有稳定的领导者,Raft 就无法运行。
leader选举中, 时间很关键。只要系统满足以下时间要求,Raft 将能够选举并保持稳定的领导者:
broadcastTime ≪ electionTimeout ≪ MTBF
在这个不等式中,broadcastTime 是服务器向集群中的每个服务器并行发送 RPC 并接收它们的响应所花费的平均时间;MTBF 是单个服务器的平均故障间隔时间。这些时间差距应该是小一个数量级的。广播时间: 0.5ms ~ 20ms, 选举超时:10ms ~ 500ms,MTBF:一般在几个月甚至更多。
6. 集群成员变更
在实际生产中,参与共识算法的服务器需要更改,例如
- 更换故障服务器
- 更改复制数量
可以手动配置,然后重启,但这会使集群在转换期间不可用, 且存在操作员错误的风险, 因此将自动化配置更改并将它们合并到 Raft 共识算法中。
为了使配置更改机制安全,在过渡期间不得有可能在同一term内选举两个领导人。不幸的是,服务器直接从旧配置切换到新配置的任何方法都是不安全的。一次原子地切换所有服务器是不可能的,因此在过渡期间集群可能会分裂成两个独立的多数(见图 10)
为了确保安全,使用两阶段修改配置。, 在 Raft 中,集群切换到联合共识的过度配置, 一旦提交了联合共识,系统就会转换到新的配置。联合共识结合了新、旧配置:
- log entries
- 来自任一配置的任何server都可以充当领导者。
- 协议(用于选举和进入承诺)要求旧配置和新配置的majorities分开
联合共识不会影响安全性的情况下,允许服务器在不同时间在配置之间进行转换。此外,联合共识让集群在配置更改期间,继续为client请求提供服务。
使用特殊的log entry 更改集群配置(图 11 说明了配置更改过程)。
leader 收到从C-old到C-new的修改请求,于是创建 C-old,new entry entry 进行联合共识,然后将这些复制到其他机器。无论这个entry 是否提交,server都会使用entry中的配置,决定未来决策。意味着leader 会使用C-old,new 的规则来决定是否提交C-old,new的entry。如果leader 奔溃,那么可能会在 Cold 或 Cold,new 下选择新的领导者,这取决于新的leader
一旦 C-old,new 被提交,Cold 和 Cnew 都不能在没有对方批准的情况下做出决定,并且领导者完整性属性确保只有拥有 Cold,new 日志条目的服务器才能被选为领导者。领导者现在可以安全地创建描述 Cnew 的日志条目并将其复制到集群。同样,此配置将在每台服务器上生效。当新配置在 Cnew 规则下提交后,旧配置变得无关紧要,可以新配置中的服务器可以被关闭。如图 11 所示,Cold 和 Cnew 不能单方面做出决定;这保证了安全性。
重新配置还有另外三个问题需要解决。第一个问题是新服务器最初可能不会存储任何日志条目。如果它们以这种状态添加到集群中,它们可能需要很长时间才能赶上,在此期间可能无法提交新的日志条目。为了避免可用性差距,Raft 在配置更改之前引入了一个额外的阶段,在该阶段中,新服务器作为非投票成员加入集群(领导者将日志条目复制给它们,但是考虑提交日志和竞选的多数不考虑他们)。一旦新服务器赶上了集群的其余部分,就开始进行重新配置的过程了。
第二个问题是集群领导者可能不是新配置的一部分。在这种情况下,leader在提交 Cnew 日志条目后下台(返回到follower状态)。这意味着将有一段时间(当它提交 Cnew 时)领导者正在管理一个不包括自身的集群;它复制日志条目,但不把自己算入majorities。leader变化发生在 Cnew 提交时,因为这是新配置可以独立运行的第一个点(总是可以从 Cnew 中选择领导者)。在此之前,可能只有来自 Cold 的服务器可以被选为leader。
第三个问题是删除的服务器(不在 Cnew 中的服务器)可能会破坏集群。这些服务器不会收到心跳,因此它们会超时并开始新的选举。然后他们将发送带有新任期号的 RequestVote RPC,这将导致当前领导者恢复为追随者状态。最终会选出一个新的leader,但是被移除的服务器会再次超时,这个过程会重复发生,导致很差的可用性。
为了防止这个问题,当服务器认为当前领导者存在时,它们会忽略 RequestVote RPC。具体来说,如果服务器在听取当前领导者的最小election selection timeout 内收到 RequestVote RPC,它不会更新其term或授予其投票。这不会影响正常的选举,每个服务器在开始选举之前至少等待一个最小election selection timeout 。它有助于避免被移除的server对集群服务造成的中断:如果领导者能够获得其集群的心跳,那么它将不会被更大的任期号所废止。
7 日志压缩
Raft 的日志在正常运行时会增长,以包含更多的客户端请求,但在实际系统中,它不能无限制地增长。随着日志变长,它会占用更多空间,并需要更多时间来重现。如果没有某种机制来国企日志信息,最终将导致可用性问题。
快照是最简单的压缩方法。在快照中,整个当前系统状态被写入稳定存储上的快照,然后所有到这一点的日志都会被丢弃,Chubby 和 ZooKeeper 中使用了快照,本节的其余部分描述了 Raft 中的快照。
压实的增量方法,例如日志清理 [36] 和日志结构合并树 [30, 5],也是可能的。它们一次对一小部分数据进行操作,将压缩的负载在时间维度上分散开来。他们首先选择一个已经积累了许多已删除和覆盖的对象的数据区域,更紧凑地重写该区域中的活动对象并释放该区域。与快照相比,这需要显着的额外机制和复杂性,快照通过始终对整个数据集进行操作来简化问题。虽然日志清理需要对 Raft 进行修改,但状态机可以使用与快照相同的接口来实现 LSM 树。
图 12 展示了 Raft 中快照的基本思想。每个服务器独立创建快照,覆盖已提交的条目。大部分工作包括状态机将其当前状态写入快照。 Raft 还在快照中包含少量元数据:包含最后entry的索引和term。这些将用于快照后第一个日志条目的 AppendEntries 一致性检查,因为该条目需要先前的日志index和term。为了启用集群成员更改功能(第 6 节),快照还包含截至最后一个索引对应的最新配置。一旦服务器完成新快照的写入,快照之前的所有entry 和之前的所有快照。
尽管服务器通常独立创建快照,但leader必须偶尔将快照发送给落后的follower。这种情况通常发生在,领导者已经丢弃需要发送给follower的entry。但这种情况通常不会出现:跟上leader的follower通常都会有改entry。但是,运行异常缓慢的follower或新加入集群的服务器(第 6 节)不会。更新这样的follower更新的方法是让leader通过网络向它发送快照.
leader使用名为 InstallSnapshot 的新 RPC, 将快照发送给落后太多的follower;参见图 13。当follower收到带有此 RPC 的快照时,它必须决定如何处理其现有的日志entry。通常,快照将包含收件人日志中尚未包含的新信息。在这种情况下,follower 会丢弃它现有的整个日志, 用快照所取代状态,并且可能有未提交的条目与快照冲突。相反,如果follower收到描述其日志前部的快照(由于重新传输或错误),则快照所涵盖的日志条目将被删除,但快照后的条目仍然有效,必须保留.
还有一种方法,只有领导者会创建一个快照,然后它将这个快照发送给它的每个follower(不是follower在本地创建快照) 但是这种方法有缺点:1. 浪费网络带宽 2.系统变得复杂:leader既要发送log entry,同时也需要发送快照。
两个影响快照性能的问题: 1. 服务器必须决定何时进行快照。如果服务器快照过于频繁,则会浪费磁盘带宽和能源;如果它的快照太不频繁,它就有耗尽其存储容量的风险,并且会增加在重新启动期间重播日志所需的时间。一种简单的策略是在日志达到固定大小(以字节为单位)时拍摄快照。如果将此大小设置为远大于快照的预期大小,则用于快照的磁盘带宽开销将很小。
- 创建快照可能会花费大量时间,导致正常操作被延缓。解决方案是使用写时复制技术,以便在不影响正在写入的快照的情况下接受新的更新。例如,使用功能数据结构构建的状态机天然支持这一点。或者,操作系统的写时复制支持(例如 Linux 上的 fork)可用于创建整个状态机的内存快照(我们的实现方法)
8 client 交互
本节介绍客户端如何与 Raft 交互,包括客户端如何找到集群领导者以及 Raft 如何支持线性化语义 [10]。这些问题适用于所有基于共识的系统,Raft 的解决方案与其他系统类似。
Raft 的客户端将所有请求发送给领导者。当客户端第一次启动时,它会连接到随机选择的服务器。如果客户端的首选不是领导者,该服务器将拒绝客户端的请求并提供有关它所听到的最新领导者的信息(AppendEntries 请求包括领导者的网络地址)。如果leader崩溃,客户端请求会超时;客户端然后再次随机使用服务器,找到leader。
我们对 Raft 的目标是实现可线性化的语义(每个操作似乎在其调用和响应之间的某个时间点立即执行一次,恰好一次)。然而如上所述,Raft 可以对一个命令执行多次。例如,如果领导者在回应client前崩溃了,但是log entry已经提交了,client将会给第二个leader重发。解决方案是让客户为每个命令分配唯一的序列号。然后,状态机跟踪为每个客户端处理的最新序列号,以及相关的响应。如果它收到命令但是其序列号已经被执行过的,它会立即响应,同时不重新执行请求。
只读操作不会将任何内容写入日志。然而,如果没有额外的措施,可能返回陈旧数据,因为响应请求的领导者可能已经被取代,但是他不知道自己被取代了。线性化读取不能返回陈旧数据,Raft 需要而不使用日志的情况下, 用两个额外的预防措施来保证这一点。1.,领导者必须拥有提交的条目的最新信息。领导者完整性属性保证了上述条件,但在其任期开始时,它可能不知道那些是提交的entry。要找出答案,它需要从其任期内提交一个条目。 Raft 通过让每个领导者在其任期开始时向日志中提交一个空白的 no-op 条目来处理这个问题。2.leader必须在处理只读请求之前,检查它是否已被罢免(如果选举了更新的领导者,其信息可能是陈旧的)。 Raft 通过让领导者在响应只读请求之前与大多数集群交换心跳消息来处理这个问题。或者,领导者可以依靠心跳机制来提供一种租约形式[9],但安全需要依赖于时间(它假设有时钟偏差)。