论文翻译-Raft共识算法

Raft是计算机领域经典算法,更是后端开发领域必读算法之一。
本文是其论文的翻译。

英文原文:

《In Search of an Understandable Consensus Algorithm(Extended Version)》

Diego Ongaro and John Ousterhout

摘要

Raft是一种用于管理复制日志的共识算法。其结果和Multi-Paxos等效,且有着相同的效率;但是Raft的算法结构和Paxos不同;这使得Raft比Paxos更易懂;"可理解性"使得我们能更好地应用这个算法。Raft提高可理解性的方式是:将共识的关键元素分离开来(如选举、日志复制和安全性),并通过强制执行更强的一致性减少了所涉及的状态数量。用户研究结果表明,对于学生而言,Raft比Paxos更容易学习。此外,Raft还提供了一个变更集群成员的机制,该机制使用"重叠多数"来保证安全性。

1. 简介

共识算法允许一组机器作为一个协调一致的群体运行,即使其中一些节点宕机也能继续对外提供服务。正因为如此,它们在构建可靠的大型软件系统过程中起到了关键作用。近十年来,Paxos[15, 16]在各共识算法中处于主导地位:大多数共识算法的实现基于Paxos或受其影响,同时Paxos也成为共识算法教学过程中的主要工具。

译者注:本论文发表于2014年

不幸的是,Paxos难以理解,尽管过去在在这方面做过很多尝试。此外,在实际落地Paxos过程中还要对其做复杂的改造。因此,开发者和学生都难以掌握Paxos。

译者注:Paxos的论文只给出了核心思想,未对一些落地过程的细节问题进行太多的描述,也未提供相关的代码库;这是"落地过程要对其做复杂改造"的原因。

经过在Paxos之中的挣扎,我们开始寻找一个新共识算法,目标是可以为系统构建和教育提供更好的基础。我们的首要目标是“可理解性”:新算法要比Paxos更容易学习;此外,我们希望该算法也能符合开发者的直觉。对于一个算法而言,"有效"固然重要,但"能让人轻松理解其为什么有效"同样不可忽视。

最终我们设计了一个名为Raft的共识算法。过程中,我们提高可理解性的方式包括解耦(Raft将Leader选举、日志复制和安全性分开考虑)和状态空间缩减(和Paxos相比,Raft降低了不确定性,并减少了节点之间不一致的场景)。我们曾对来自两所大学共43名学生进行过研究;结果表明,与Paxos相比,Raft更容易理解:在学习这两种算法后,其中的33名学生在回答Raft问题时表现更好(相比于在Paxos问题下的表现)。

Raft与现有的共识算法在许多方面有相似之处(尤其是Okasaki和Liskov的VR算法[29, 22]),但它有以下几个新特性:

  • 强Leader:Raft中的Leader角色比其他共识算法更强势。例如,日志条目只从Leader流向其他服务器。这简化了复制日志的管理,并使Raft更容易理解。
  • Leader选举:Raft使用随机时间戳来选举领导者。这仅仅在心跳过程中增加了少量额外机制(现有共识算法也都涉及心跳机制),使得算法能以简单且迅速的方式解决冲突。
  • 成员变更:Raft提供了一种新的变更集群成员的机制,该机制在转换过程中使两个不同配置版本下的"多数集"重叠,进而实现"联合一致性"。这使得集群能够在配置更改期间继续对外提供服务。

我们相信,Raft优于Paxos和其他共识算法,无论是用于教育目的还是作为实现的基础。它比其他算法更简单、更容易理解;它的描述足够详细,可以满足实际系统的需求;已经有几个开源实现,并被几家公司使用;其安全性得到了正式定义并得到证明;其效率与其他算法相当。

本文接下来的部分会介绍复制状态机问题(第2节),讨论 Paxos 的优缺点(第3节),给出我们提升可理解性的一般方法(第4节),描述Raft 算法(第5~8节),分析评估Raft算法(第 9 节)并讨论相关工作(第10节)。

2. 复制状态机

共识算法通常用于实现“复制状态机”[37]。在这种方案中,相同的状态机被复制到多个节点,他们的计算结果完全一致,并且可以在某些节点宕机的情况下继续运行。复制状态机用于解决分布式系统中的各种容错问题。例如,拥有单个集群Leader的大型系统(如GFS[8]、HDFS[38]和RAMCloud[33])通常使用单独的复制状态机来管理领导者选举,并存储相关配置信息,这些信息即使在Leader宕机的场景下也必须保证可靠。复制状态机的例子包括Chubby[2]和ZooKeeper[11]等。

复制状态机通常使用复制日志来实现,如图1所示。每个节点都维护了包含一系列指令的日志,且各节点会将本地日志中的指令按顺序交由状态机执行。每各节点的日志中都存储了相同的指令序列,因此每个节点的状态机都按相同的指令序列进行执行。每个节点的状态机都会计算出相同的状态和输出结果,因为状态机是确定性的。
图1

图1:复制状态机的架构。共识算法为客户端维护了复制过的日志,日志中包含一系列状态机指令。各状态机严格按照日志中的指令序列执行,所以这些复制状态机有相同的产出。

保持复制日志的一致性是共识算法的职责。节点上的共识模块接收来自客户端的命令,并将其添加到日志中。 共识模块与其它节点上的共识模块通信,以确保每个日志最终包含相同的请求序列,即使某些节点宕机。 一旦被正确复制,每个节点的状态机就按日志中的顺序处理指令;最终,相关输出被返回给客户端。 如此,这些节点从外部看起来就像单个高可靠性的状态机。

实际系统中的共识算法一般具备如下特性:

  • 他们保证在所有非拜占庭条件下安全(永远不会返回错误的结果),包括网络延迟、分区、包丢失、复制和重新排序。

  • 只要大多数服务器能够正常工作并相互通信,它们就能完全发挥作用。因此,一个典型的由五台服务器组成的集群可以容忍两台服务器故障。假设服务器会因为停止而失败;随后可以从持久化存储中恢复并重新加入群集。

  • 他们不依赖时间来确保日志记录的一致性:故障时钟和极端消息延迟可能导致可用性问题。

  • 在一般情况下,一个命令只要被集群中的大多数节点响应了单轮远程过程调用就可完成;少量慢服务器不会影响整体系统性能。

3. Paxos有什么缺点?

在过去的十年里,Leslie Lamport的Paxos协议[15]已经几乎成为"共识"的代名词:其既是教学中最常讲解协议,同时也是大多数实现的基础。Paxos首先定义了一个能够就单一决策达成一致的协议(比如单条被复制的日志条目)。我们称这个子集为Single-Paxos。然后,Paxos将多个这样的协议组合在一起,以便进行一系列决策(Multi-Paxos)。Paxos确保了安全性和有效性,并支持集群成员关系的变化。不仅其正确性得到了证明,而且Paxos在一般的场景下也很高效。

不过,Paxos有两个显著的缺点。第一个缺点是Paxos难以理解。完整的解释[15]出了名的难懂;很少有人能真正理解它,而且需要付出很大的努力。因此,人们尝试用更简单的术语来解释Paxos[16, 20, 21]。这些解释主要侧重于单决策的子集,不过这个过程也仍然不太容易。在对2012年 NSDI与会者的非正式调查中,我们发现即使是经验丰富的研究人员,也很少对Paxos得心应手。我们自己也一直在努力解决Paxos的问题;我们在研究了几个简化版本的解释、设计了自己的替代协议,之后才最终理解了整个Paxos协议,这个过程耗时将近一年。

我们假设Paxos的复杂性来自于它选择单命令子集作为基础。 Single-Paxos紧凑而微妙: 它分为两个阶段,这两个阶段既没有符合直觉的解释,也不能被拆分开来独立理解。 因此很难直观地理解Single-Paxos的有效性。 Multi-Paxos的组合规则显著增加了复杂性和微妙性。 我们认为就多个决策达成共识(即整个日志而不是单个条目)的大问题可以拆分成更直接、明显的子问题。

Paxos的第二个问题是其没有为实际实现提供良好的基础。这样说的原因之一是Multi-Paxos没有被广泛接受的算法版本。Lampor 的描述主要集中于Single-Paxos;他虽简要描述了实现Multi-Paxos的可能方案,但缺少细节描述。人们在扩展和优化Paxos这件事上做过很多尝试,比如[26]、[39]和[13],但是各方案彼此之间有差异,和Lamport描述的算法框架也有差异。另外像Chubby[4]这类系统实现了类Paxos的算法,但多数场景下,其实现细节并未公开。

此外,Paxos的架构对于构建实际系统来说并不友好;这是单命令拆解的另一个后果。例如,选择一组独立的日志条目并将它们合并为一个顺序日志这事没有好处;这种方案只会增加复杂性。围绕整个顺序日志设计一个系统要简单得多。另一个问题是Paxos核心逻辑采用了无主模式,各节点地位相同(尽管Paxos最终为了性能考虑而建议使用弱领导形式)。在一个只做单一决策的理想化世界里,这种方法是有意义的,但实际系统很少使用这种方法。如果需要做出一系列决定,那么首先选举出Leader,然后让Leader协调决策,这种方案会更简单、更快捷。

因此,实际系统与Paxos有很大差异。每个实现都从Paxos开始,然后发现它的实现难度,最后开发出一个和Paxos不同的架构。这既耗时又容易出错,并且Paxos的理解难度使该问题更加严重。Paxos的公式可能对于正确性证明是有用的,但实际系统与Paxos有很大的不同,证明几乎没有什么价值。Chubby 实现者的以下评论具有代表性:

Paxos算法的描述和实际系统的需求之间存在重大差距…最终,系统将以一个未经验证的协议为基础。

由此,我们得出结论:Paxos既不适合系统构建也不适合教学。鉴于“共识”在大规模软件系统中的重要性,我们开始研究比Paxos更好的替代算法。Raft就是所产出的算法。

4. 为可理解性设计

在设计Raft时,我们设定了几个目标:该算法必须为系统构建提供一个完整而实用的基础设施,这可以显著减少开发人员的设计工作量;算法必须在所有情况下都是安全的,并且在典型场景下可用;对常见的操作而言,算法必须是高效的。但最重要的,也是最困难的目标是"可理解性"。算法必须能够容易被广大受众理解。此外,算法还必须符合人们的直觉,这样开发者才能对其进行扩展(在实际开发过程中,扩展几乎是不可避免的)。

在Raft的设计中,在很多场景下,需要我们在不同方案之间进行选择。这时,我们会根据可理解性对备选方案进行评估:每个替代方案解释难度有多大(例如,其状态空间有多复杂?是否隐含着微妙的意义?)以及读者完全理解该方案及其实现的难度是怎样的?

诚然,这种分析具有高度的主观性;不过,我们使用了两种可被普遍接受的方法。 第一种是问题分解法:我们会尽可能把问题分成独立的子问题,各子问题可以相对单独地解决、解释和理解。例如,在Raft中,我们将方案拆解成领导者选举、日志复制、安全性以及成员变更等独立的部分。

第二种方法是通过减少需要考虑的状态数量来简化状态空间,从而使系统更易于理解,并在一定程度上减少不确定性。具体来讲就是不允许日志有“空洞”,Raft限制了日志之间可能出现不一致的方式。尽管在大多数情况下,我们试图消除不确定性,但在某些场景下,不确定性实际上会提高可理解性。特别是,随机化方法引入了不确定性,但这使得可以通过以相似的方式处理所有可能的选择(“随便选,问题不大”),进而减小状态空间。我们使用随机化的方案来简化Raft领导者选举算法。

5. Raft共识算法

Raft是一种用于管理日志复制的算法,如第2节所述。图2总结Raft算法,图3列出了该算法的关键属性;本节余下部分将按顺序讨论这些图中的内容。
图2

图2:Raft共识算法的简要总结(不包括成员变更和日志压缩)。左上角描述了一组独立的、可重复触发的规则,用以描述节点的行为。[31]更加精确地描述了该算法。

图3

图3:Raft确保这些属性在任何时候都是成立的。

Raft首先选举一个特定的Leader,然后将日志复制管理职责完全赋予该Leader,进而实现“共识”。 Leader接收来自客户端的日志条目, 在其他服务器上进行复制,并通知各节点何时可以安全地将日志条目应用于状态机。 Leader角色的引入可以简化日志复制的管理。例如,Leader可以不查询其他节点即可决定在哪里为日志添加新条目,数据从Leader单向流向其他节点。 Leader可能会崩溃或与其它节点断开连接,在这种情况下会重新选举一个新的Leader。

在引入Leader的方案中,Raft将共识问题分解为三个相对独立的子问题,接下来会逐个讨论:

  • Leader选举: 现有Leader崩溃时必须重新选举一个Leader;

  • 日志复制: Leader必须接受来自客户端的日志条目,并将其复制到集群中其他节点,并强迫其他节点接受自己的数据;

  • 安全性: Raft关键的安全属性指的是图3中的状态机安全:如果任何节点已将指定日志条目应用于状态机,则其他节点不得在同一个位置(index)应用不同的命令。5.4节描述了Raft时如何保证此属性的;其中会对5.2节中描述的选举机制施加额外限制。

在介绍一致性算法之后,本小节将讨论可用性问题以及计时器在系统中的应用。

5.1 Raft基础知识

一个Raft集群包含多个节点;五个是最常见的数量,其能够容忍两个节点的故障。无论任何时候,每个节点都处于三种角色之一:Leader、Follower或Candidate。在正常运行时,有且只有一个Leader,其他所有节点都是Follower。Follower是被动的:它们不会自行发出请求,而是只响应来自Leader和Candidate的请求。Leader处理所有客户端请求(如果客户端连接到Follower,则Follower会将其重定向到Leader)。第三个角色Candidate用于选举新的Leader,如5.2小节所述。图4显示了角色及其转换;下面将讨论角色迁移。
图4

图4:节点角色。Followers只对其他节点的请求进行响应。如果Follower长时间未收到请求,其将转变为Candidate角色,并发起一轮选举。获得大多数节点选票的Candidate将成为新Leader。通常来讲,Leader若未宕机,就会频繁向外发送请求。

Raft将时间划分为任意长度的周期(term),如图5所示。用连续整数为term编号。每个term都以选举开始,选举中一个或多个Candidate试图成为Leader(见5.2节)。如果一个Candidate在选举中获胜,则在该term其余时间内担任Leader。在某些情况下,选举会走向"分裂"。在这种情况下,该term将在无Leader的情况下结束;一个新的term(以及新的选举)将马上开始。Raft确保在一个term中至多有一个Leader。
图5

图5:时间被划分成term,每个term由一次选举开始。选举成功后,单一Leader将掌管整个集群,直到term结束。有时候选举也会失败,这些场景下term以无Leader的结果结束。多个节点间对term变迁的感知不保证一致。

不同节点可能会在不同的时间感知到term之间的过渡,而在某些情况下,某些节点甚至可能感知不到到选举或整个term。term在Raft中起到逻辑时钟的作用[14],借助term,节点能够检测到过时的信息,例如过时的Leader。每个节点存储一个当前term的编号,随着时间单调递增。节点间每次通信时,都会交换当前term信息。如果一个节点的当前term比另一个小,则该节点会将其term更新为较大的值。如果Candidate或Leader发现其term已过期,则会立即将自己的角色重置为Follower 。如果节点收到带有过时term的请求,则会直接拒绝处理。

Raft服务器通过远程过程调用(RPC)进行通信,而基本共识算法只需要两种类型的RPC。Candidate会在选举期间发起RequestVote RPC调用(见5.2节),Leader会发起AppendEntries RPC调用来复制日志条目并提供心跳信号(见5.3节)。第7节添加了第三个RPC,用于在节点之间传输数据快照。节点若没有及时收到响应就会重试RPC;并且因性能方面考虑,各RPC之间是并行关系。

5.2 Leader选举

Raft使用心跳机制来触发Leader选举。节点启动时,初始角色是Follower。只要Follower节点能持续接收到来自Leader或Candidate的有效RPC,其就会维持在Follower角色。 Leader会定期向所有Follower发送心跳(不包含日志条目的 AppendEntries RPC),用以维持自己的"权威"。 如果Follower在选举超时时间内没有收到任何通信,就会认定当前没有有效的Leader,那么它就会发起新一轮选举来选出新的Leader。

为了开启新一轮选举,Follower会增加它的当前term,并转换为Candidate角色。然后它为自己投票,并向集群中的每个节点并行发出RequestVote请求。节点会停留在Candidate角色,直到发生下列三种情况之一:(a) 它赢得了选举,(b) 另一台服务器宣布自己为Leader,或者 © 一段时间过去了但没有选出Leader。下文将逐个讨论这几种场景。

Candidate在获得集群中多数节点的选票时,就赢得了选举。每个节点在一个给定term中最多只投一票,过程中采用先到先得原则(注意:5.4 节为投票增加了额外限制)。多数选票规则确保了某个term中至多一个Candidate能够赢得选举(图3中的选举安全属性)。Candidate一旦赢得选举,就会成为Leader。然后它会向所有其他节点发送心跳消息以建立权威并抑制新的选举。

在等待投票时,Candidate可能会收到另一个声称是Leader节点的AppendEntries RPC。如果该Leader的term(包含在它的RPC中)至少与本节点的当前term一样大,则Candidate将承认该Leader,并将自己重置为Follower。如果RPC中的term小于Candidate的当前term,则本节点会拒绝该RPC并继续处于Candidate角色。

第三种可能的结果是Candidate既没有赢得选举也没有输掉选举:如果许多Follower同时成为Candidate,选票可能会分散,使任何候选人都无法获得多数票。当这种情况发生时,每个Candidate都会超时并开始新一轮选举,方式是递增term值并发起另一轮RequestVote RPC。然而,如果不引入额外的措施,这个过程可能会无限重复。

Raft使用随机的选举超时来确保上述选票分散场景的发生频率较低,并且能够快速收敛。为了从源头抑制选票分散,选举超时时间从一个固定的区间中随机选择(例如,150-300ms)。这使得节点分散开来,因此在大多数情况下只有一个节点会过期;该节点会赢得选举并在其他任何节点选举超时之前发送心跳。当出现选票分散的情况时也用同样的办法解决。每个Candidate在选举开始时重置其随机选举超时计时器,并等待该超时时才开始下一轮选举;这降低了新选举中再次发生投票分散的可能性。9.3节得出了一个结论:这种方法可以迅速选出Leader。

选举是按"可理解性优先"原则进行决策的一个例子。最初,我们计划使用排名系统:每个Candidate分配一个唯一的排名,使用该排名在Candidate之间进行选择。如果一个Candidate发现另一个Candidate的排名更高,它会重置为Follower角色,这样,排名更高的Candidate更容易赢得下一次选举。我们发现这种方法会引发微妙的可用性问题(当排名较高的节点宕机时,排名较低的节点需要超时后才能再次成为Candidate,如果过早这样做,则可能会重置选举的进度)。我们对算法进行了多次调整,但每次调整后都会出现新的边界场景。最终,我们得出结论:随机重试方法更直观、更容易理解。

5.3 日志复制

一旦选举出Leader,它就开始接收客户端请求。每个客户端请求都包含一个要由复制状态机执行的指令。 Leader将该指令作为新日志条目追加到本地日志,然后并发地向其他节点发出AppendEntries RPC以复制该条目。 当日志条目安全复制(会在下文进行描述)后,Leader会将其应用于本地状态机并将执行结果返回给客户端。 如果Follower崩溃或运行缓慢,或者网络数据包丢失,那么Leader将持续重试AppendEntries RPC(即使已经将响应发给了客户端),直到所有Follower最终存储了所有日志条目为止。

日志的结构如图6所示。每个日志条目都存储了一个状态机指令,以及该条目被Leader接收时的term值。在日志条目的term值用于检测日志间的不一致,以确保图3中的一些性质。每个日志条目还有一个整数index,用于标识该条目在整个日志中的位置。
图6

图6:日志由日志条目组成,日志条目用连续整数进行编号。每个日志条目包含了其被创建时的term(对应图中每个方格内部的数字),以及一个适用于状态机的指令。当日志可以安全地由状态机执行时,我们称该日志条目已被提交(committed)。

Leader决定何时将日志条目应用到状态机是安全的;我们称满足条件的条目为“已提交(committed)”。Raft保证提交的日志条目会被持久化存储且最终会被所有可用的状态机执行。一旦创建条目的Leader在多数节点上完成了复制(例如,图6中的条目7),就会提交该条目。这也会提交Leader日志中的所有前驱条目,包括由之前的Leader创建的条目。5.4节讨论在Leader发生变更后应用此规则时的细节,并证明这个提交口径是安全的。Leader会维护其感知到的已提交的最大index,并将其通过AppendEntries RPC(包括心跳消息)传给其他节点。一旦Follower感知到某日志条目已提交,它就在本地状态机中按日志顺序执行对应的条目。

我们设计了Raft日志机制来维护不同节点日志之间的高一致性。这不仅简化了系统的操作并使其更具可预测性,而且该机制是保障算法安全性的重要组成部分。Raft维护以下属性,这些属性共同构成了图3中的“日志可对齐”属性:

  • 如果两个不同日志中的条目具有相同的index和term,则它们所存储指令也相同。

  • 如果两个不同日志中的条目具有相同的index和term,则两日志中所有前驱条目都是相同的。

第一个性质源于Leader在一个term中最多只创建一个具有给定index的日志条目,而日志条目日志中的位置永远不会变。第二个性质是由AppendEntries RPC执行的一个简单的一致性检查保证的。当发送AppendEntries RPC时,Leader会包括其日志中紧接新条目前面的条目的index和term。如果Follower在其日志中找不到具有相同index和term的条目,则它拒绝接受新条目。一致性检查使得如下归纳过程成立:日志初始数据为空,满足"日志可对齐"属性,并且无论何时追加日志条目,一致性检查都会使该属性仍然成立。因此,只要AppendEntries RPC成功返回,Leader就知道Follower的日志与自己的日志中新条目及之前的条目完全相同。

在正常运行时, Leader和Follower的日志可以保持一致, 因此AppendEntries RPC的一致性检查永远不会失败。然而, Leader崩溃可能会导致日志不一致(老的Leader可能还完成全部日志条目的复制)。这些不一致可能会在一系列的Leader或Follower的崩溃中不断累积。图7描述了Follower日志与新Leader日志可能存在差异的场景。一个Follower可能缺少Leader的某些条目,也可能包含Leader上没有的额外条目,或者兼而有之。这类不一致可能会跨越多个term。
图7

图7:当Leader(图中第一行)当选时,Follower日志的状态可能会处于(a-f)任意一种。每个方框代表一个日志条目;方框中的数字表示term。Follower可能缺少条目(a-b),可能有额外未提交的条目(c-d),或者两者兼而有之(e-f)。例如, 场景f可能会在如下过程后出现:该节点在term 2下是领导者,向其日志添加了几个条目,然后在提交之前崩溃;它很快重新启动并成为term 3 的Leader,并向其日志添加了一些额外的条目;在term 2或term 3所有条目都未提交之前,节点再次崩溃,宕机状态跨越了多个term。

在Raft算法中,Leader通过强制Follower接受自身数据来消除不一致。这意味着Follower日志中的冲突条目将被来自Leader的日志条目所覆盖。5.4节会进一步说明,在施加一些约束条件后,这种覆盖操作时安全的。

为了使Follower的日志与自己的保持一致,Leader必须找到两个日志能达成一致的最新日志条目,然后删除Follower日志中该条目之后的所有条目,并向Follower发送该条目之后的所有Leader的条目。这些操作都是使用AppendEntries RPC的一致性检查规则实现的。Leader维护每个Follower的nextIndex,即Leader将要发送给该Follower的下一个日志条目的index。 当Leader刚选举成功时,它会将所有nextIndex值初始化为本地日志中的最后一个条目的index加1(图7中的11)。 如果Follower的日志与Leader的不一致,则 AppendEntries RPC一致性检查将失败。在被拒绝后,Leader会递减nextIndex并重试 AppendEntries RPC。最终,nextIndex将达到一个Leader和Follower日志能匹配的位置。 这时,AppendEntries RPC将被成功执行,过程中会消除Follower中所有与Leader冲突的日志条目并追加来自Leader日志的后续条目(如果有的话)。一旦AppendEntries RPC执行成功,Follower的日志就会与Leader相同,并且在当前term后续时间中保持一致。

如果需要,可以优化协议以减少被拒绝的AppendEntries RPC数量。例如,在拒绝 AppendEntries请求时,Follower可以包括冲突项的term以及它该term包含的第一个index。有了这些信息,Leader在缩小nextIndex过程中可以一次性跳过所有与该term相关的冲突条目;这样,冲突检测时就是每个term对应一个AppendEntries RPC而非每个条目一个RPC。不过,我们觉得在实际开发中这种优化不一定时必要的,因为失败不频繁,并且不太可能有大量不一致的条目。

借助冲突检测机制,当一个Leader选举成功后,其不需要采取任何特殊的措施来恢复日志一致性。 Leader只需正常运行,并且在出现一致检查失败时,日志会自动收敛。Leader永远不会覆盖或删除其自身的日志(图3中的"Leader只追加数据"属性)。

这种日志复制机制体现了第2节中描述的理想一致性属性:Raft只要大多数节点处于存活,就可以接受、复制并应用新的日志条目;在正常情况下,可以使用单个远程过程调用 (RPC) 将新条目复制到集群中的大多数节点;一个速度不佳的Follower不会影响整体性能。

5.4 安全性

前面几节描述了Raft如何选举Leader和复制日志条目。然而,到目前为止所描述的机制并不足以确保每个状态机以相同的顺序执行相同的命令。例如,在Leader提交多个日志条目时,一个Follower能无法访问,然后它可能会被选为Leader并用新的条目覆盖这些条目;因此,不同的状态机可能会执行不同的命令序列。

本节在选举过程中添加额外的限制约束,使得Raft成为一个完备的算法。这些约束确保了对于任何给定term,Leader包含了所有先前term中提交的所有条目(参见图3中的“Leader完备性”)。在选举限制中,我们进一步明确了提交的规则。最后,我们给出了一个关于Leader完备性的证明,并解释了这个属性是如何保障复制状态机正确性的。

5.4.1 选举约束

所有基于Leader的共识算法中,Leader都必须最终存储所有已提交的日志条目。 在某些共识算法中,例如ViewstampedReplication[22],最初没有包含所有已提交的条目的节点也可以被选举为Leader。这些算法包含额外的机制来识别缺失的条目,并在选举过程中或稍后将缺失的条目传给新Leader。不幸的是,这将引入不可忽视的额外机制和复杂性。Raft使用一种更简单的方法,过程中会保证新Leader自选举成功那一刻就包含了之前Leader提交过的所有条目,无需额外的传输流程。这意味着日志条目仅单项传播,即从Leader到Follower,Leader永远不会覆盖本地日志中的现有的条目。

Raft在投票过程中会阻止未包含全部已提交条目的Candidate赢得选举。Candidate必须通过RequestVote RPC请求大多数节点并获得选票后才能当选,这意味着每个已提交的条目都至少出现在其中一个节点上。如果Candidate的日志未“落后”于该多数派中的任何其他节点(下文会明确定义"新"/"落后"的口径),则可认为其包含了所有已提交的条目。 RequestVote RPC中有一个限制:请求中包含Candidate日志的信息,如果RPC接收者的日志比Candidate新,则将会拒绝为其投票。

Raft通过比较日志中最后条目的term和index来确定两个日志哪个更"新"。如果两日志最后条目的有不同term值,那么term较大的日志更新。如果两日志条目term相同,则index较大的日志更新。

5.4.2 提交之前term的条目

如5.3节所述,当Leader确认当前term的一个条目已复制到大多数节点上时,它就会认定该条目已被提交。如果Leader在提交一个条目前崩溃,那么未来的Leader将尝试完成该条目的复制过程。不过,即使大多数节点上都有来自之前term的某个条目,新Leader也不能立即认定该条目已被提交。图8描述了一个特殊场景,其中老的日志条目存储在大多数节点器上,但仍然可以被新Leader覆盖。
图8

图8:给出了一个时间线,说明为什么Leader不能用来自旧term的日志条目来确定提交结果。(a)中,S1是领导者,并部分复制了索引为2的日志条目。在(b) 中,S1崩溃;S5取得了S3、S4和自身的选票,当选为term 3的Leader,并接受了一个新条目,index为2。在©中,S5崩溃;S1重启并当选为Leader,并继续复制。此时,来自term 2的条目已在大多数节点上复制,但尚未提交。如果S1 又崩溃,如(d),则S5可能又会被选举为Leader(获得了S2、S3和S4的选票),并用它自己的来自term 3的条目覆盖其他节点同index条目。但是,如果S1在崩溃之前在大多数节点上完成了term 4日志条目的复制,如(e)所示,则该条目已提交(S5 无法赢得选举)。此时,所有之前的日志条目也都已提交。

为了消除图8中所示的问题,Raft不会通过计数被复制的节点数来提交前一个term的日志条目。只有来自Leader当前term的日志条目的提交才通过计数的方式进行;一旦当前term中的条目被以这种方式提交,则根据"日志可对齐"属性,所有以前的条目都会间接被提交。虽然在某些情况下,可以安全地认定较旧的日志条目已被提交(例如,该条目存储在每个节点上),但是出于简单起见,Raft采用了更加保守的方案。

Raft通过提交规则引入了这种复杂性,因为当Leader复制来自先前term的日志条目时,日志条目保留其原始term编号。在其他共识算法中,如果新的Leader重新复制来自以前“term”的条目,则必须使用它的新“term”来完成。Raft的方法使推导日志条目的过程更容易,因为它们会随着时间的推移以及在不同日志之中保持相同的term编号。此外,在 Raft 中,新的Leader比其他算法所需发送的来自前一个term的日志条目更少(其他算法需要在提交之前发送冗余的日志条目来更新"term"编号)。

5.4.3 安全性论证

现在已经介绍了完整的Raft算法,我们可以更准确地论证“Leader完备性”了(该论证基于安全证明;见9.2节)。我们假设“Leader完备性”不成立,然后证明其是自我矛盾的。即设定term T的领导者(LeaderT)提交了一个当前term的日志条目,但该日志条目未被未来term的Leader所存储。考虑大于T的最小term U,其中LeaderU未存储该条目。

  1. 在选举时,已提交的条目一定不存在于LeaderU的日志中(Leader永远不会删除或覆盖条目);
  2. LeaderT在集群中的大多数节点上复制了该条目,而LeaderU从集群中大多数节点收到了投票;因此,至少一个节点(投票者)既接收了来自LeaderT的此条目,又投票给了LeaderU,如图9所示;该投票者是自我矛盾的关键;
  3. 该投票者一定是在投票给LeaderU之前就接收到了LeaderT所提交的条目;否则它会拒绝LeaderT的AppendEntries RPC请求(因为其当前term将会大于 T);
  4. 该投票者在投票给LeaderU时就已经存储了该条目;因为根据假设,每个参与的Leader都包含该条目,而Leader永远不会删除条目,而Follower只有在与Leader冲突时才会删除条目;
  5. 该投票者将其选票投给了LeaderU,因此LeaderU的日志不会落后于该投票者的日志。这导致了两个矛盾中的第一个;
  6. 首先,如果投票者最新日志条目和LeaderU最新日志条目的term相同,那么意味着LeaderU的日志不会比该投票者落后,即包含此投票者的所有日志条目;这就和假设矛盾了(两个矛盾中的第二个);
  7. 否则,LeaderU最后一个日志条目的term肯定大于投票者。进一步,该term应该比T还大,因为投票者的最新条目的term至少为T(它包含termT中提交的日志条目)。创建LeaderU最后一个日志条目的老Leader肯定在其日志中包含了此条目(通过假设)。那么,根据日志匹配性质,LeaderU的日志也肯定包含此条目,这和假设矛盾(推导出第二个矛盾的第二条分支链路);
  8. 至此,所有逻辑分支上均推导出了矛盾。因此,所有term大于T的Leader肯定包含在term T中提交的所有条目;
  9. 日志匹配属性保证未来的Leader也会包含间接提交的日志条目,比如图8(d)中的index 2;
    图9

图9:如果S1(term T的Leader)在其term中提交了一个体质条目,然后S5在Term U被选举为Leader,那么至少有一个节点(S3)接受了该条目并投票给S5。

有了Leader完整性属性,我们可以证明图3中的“状态机安全性”;该属性指的是,如果节点在给定index位置应用了某日志条目,则不会有任何其他节点在相同index执行不同的日志条目。当节点将其日志条目应用于本地状态机时,在该日志条目之前,该节点和Leader的日志肯定完全一致,并且该条目肯定已被提交。现在考虑在给定index位置上任意节点所引用日志条目的最小term;日志完整性保证所有更高term的Leader都将存储相同的日志条目,因此在后续term中,该index处只会应用相同的值。因此,状态机安全性成立。

最后,Raft要求各节点按日志index顺序应用日志条目。结合状态机安全性,这意味着所有节点都将以相同的顺序在状态机中执行完全相同的指令。

5.5 Follower和Candidate的崩溃

之前我们专注于讨论Leader的崩溃场景。而Follower和Candidate的崩溃要简单得多,而且这两者的崩溃处理是相同的。 如果一个Follower或Candidate崩溃了,则后续发送给它们的RequestVote RPC和AppendEntries RPC请求都将失败。Raft通过无限重试来处理这些故障; 如果发生崩溃的节点重新启动, 则该请求被正常处理。 如果在发送响应之前崩溃, 则它在重新启动后将再次收到相同的RPC。而Raft RPC是幂等的, 因此不会有任何影响。例如,如果一个Follower接收到一个包含日志条目的AppendEntries RPC请求,但这些条目已经存在于本地日志了, 那么该节点会忽略请求中的日志条目。

5.6 时间和可用性

我们对Raft的要求之一是,其安全性不能依赖于时间:系统不能仅仅因为某些事件发生得比预期更快或更慢就产生不正确的结果。然而,可用性(系统及时响应客户端的能力)必然是基于时间的。例如,如果消息交换的时间超过一般的节点崩溃判定时间,Candidate将没有足够的时间来赢得选举;再者如没有一个稳定的Leader,Raft算法将无法向前推进。

Leader选举是Raft中最依赖时间的阶段。只要系统满足以下的时间要求,Raft就能够选举并维护一个稳定的Leader:

broadcastTime ≪ electionTimeout ≪ MTBF

在不等式中,broadcastTime是节点向集群中的每个节点并行发送RPC并接收到响应所需的平均时间;electionTimeout是5.2节中提到的选举超时时间;MTBF是单个节点故障之间的平均间隔时间。

译者注:MTBF = Mean Time Between Failures,是可用性领域的重要概念。如果对这方面感兴趣的话可以留言,我翻译一些这方向的经典论文。

broadcastTime比electionTimeout小几个数量级,以便Leader可以可靠地发送心跳消息,用以抑制Candidate启动选举;考虑各节点选举超时时间是随机指定的,该不等式也使得"脑裂"的case不会出现。electionTimeout应比MTBF小几个数量级,以便系统可以稳定地向前推进。当Leader崩溃时,系统将在electionTimeout左右的时间内无法访问;我们希望这部分占比很小。

broadcastTime和MTBF是底层系统的固有属性,而electionTimeout则·是我们必须要去选择的。Raft的RPC通常要求接收者将信息持久化到稳定的存储介质中,因此,broadcastTime可能在0.5 ms到20 ms之间,具体取决于存储的技术选型。 因此,broadcastTime可以选择在10 ms到500 ms之间的某个值。 而MTBF为一般几个月甚至更长的时间,很容易满足上述时间要求。

6. 集群成员的变更

到目前为止,我们一直假定集群配置(参与共识算法的节点集合)是固定的。而在实践中,偶尔会需要更改配置,例如在节点故障时替换节点或更改副本数。虽然可以过将整个群集下线、更新配置文件、重新启动群集来完成此操作,但这将使得群集在配置变更期间不可用。此外,如果涉及需要手动操作的步骤,则会引入误操作的风险。为了避免这些问题,我们将配置变更自动化,并将其纳入Raft共识算法中。

为了保证配置变更机制是安全的,必须确保在转换过程中不会同时选举出两个term相同的Leader。不幸的是,任何从旧配置直接切换到新配置的方案都是不安全的。无法同时且原子性地切换所有节点的配置,因此在变更期间集群可能会分裂为两个独立的“多数派”(见图 10)。
图10

图10:因为不同节点配置切换的时间无法保证严格一致,所以从一个配置直接切到另一个配置是不安全的。在本图实例中,我们要将集群节点数量由3扩容至5。不幸的是,存在一个时间点,集群会选出两个term值相同的Leader;其中一个是由老配置(记为COld)中节点的选举结果,另一个是新配置(记为CNew)中节点的选举结果。

为了保证安全性,配置变更必须使用两阶段方案。这个两个阶段有多种实现方式。例如,一些系统(如[22])使用第一阶段来禁用旧配置,使其无法处理客户端请求;然后在第二阶段启用新配置。而在Raft中,集群首先切换到一个称为“联合共识”的过渡配置;一旦该配置被提交,系统就会转换为新的配置。“联合共识”结合了新旧两版本配置:

  • 日志条目会复制到两配置中加一起的所有节点;

  • 两种配置中的任一节点都可以当选Leader;

  • 决策(选举和日志条目的提交)需要同时得到旧配置和新配置的多数选票;

“联合共识”允许在不牺牲安全性的前提下,在不同的时间变更各节点的配置。此外,联合共识还允许群集在整个配置变更期间继续为客户端提供服务。

集群配置以日志副本的特殊条目的形式在集群中存储和传输;图11描述了配置变更流程。当Leader收到一个从COld到CNew的配置更改请求时,它会将联合一致的配置 (在图中表示为C(old,new))存储为日志条目并使用之前描述的机制来将其复制到其他节点。一旦某个节点将其新的配置条目添加到其日志中,它就会一直使用该配置来进行后续决策(节点总是使用其日志中的最新配置,不管该条目是否已提交)。这意味着,Leader将根据基于C(old,new)的规则来判定C(old,new)何时可提交。如果Leader崩溃了,新Leader可能会在COld或C(old,new)中产生,具体取决于胜选的Candidate是否收到了C(old,new)。在此期间,无论如何都不能基于CNew单方面做出决策。
图11

图11:配置变更的时间线。横虚线表示已被创建但未被提交的配置条目,而实线表示已被提交的最新配置。Leader首先创建一个联合配置C(old,new)的日志条目,并根据该配置进行提交(要分别复制到COld的多数节点和CNew的多数节点)。然后Leader创建CNew对应的日志条目,并根据CNew配置进行提交。不存在COld或CNew可单独做决策的时间点。

一旦C(old,Cnew)被提交,COld和CNew都不能在未经对方批准的情况下做出决策,并且领导者完整性确保了仅带有C(old,new)日志条目的节点才能被选举为Leader。现在,领导者可以安全地创建一个CNew的对应的日志条目并将其复制到群集中。再次强调,配置只要被节点接收,就会立即生效。当根据CNew的规则提交新配置时,旧配置就不在被使用,未被引用的节点可以关闭了。如图11所示,在任何时间点,COld和CNew都不能单独做出决策,进而保证了安全性。

关于配置变更还有三个问题需要解决。 第一个问题,新的节点可能最初不存储任何日志条目。 如果它们在这种状态下被添加到群集中,那么它们可能需要很长的时间才能跟上进度,在此期间可能无法提交新的日志条目。 为了防止可用性的中断,Raft在配置更改之前引入了一个额外阶段,在这个阶段中,新节点以非投票成员的身份加入集群(Leader会将日志条目复制给它们,但在判定是否集齐多数派回应时不会考虑这些节点)。当新节点追上了群集中的其他节点后,就配置变更过程就可以按照上面描述的方式进行处理了。

第二个问题,原集群Leader可能不在新配置之中。在这种情况下,一旦Leader提交了新的日志条目CNew,它就会被降级(重置为Follower)。这意味着存在一个时间段(Leader正在提交CNew的过程中),其中Leader在维护一个不包含它本身的集群;该Leader也会复制日志条目,但不将自己不计入多数派。当CNew被提交时会发生Leader变换,因为这是新配置可以独立做决策的第一个时间点(始终可以从CNew中选择领导者)。在此之前,只有来自COld的节点才能被选举为Leader。

第三个问题,已删除的节点(不在CNew中的节点)可能会干扰集群。这些节点不会接收到心跳信息,所以它们会超时并开始新的选举。然后,它们会发送带有新任期编号的 RequestVote RPC请求,这会导致当前的Leader重置为Follower。最终会选出一个新的Leader,但已删除的节点又会超时,从而导致重复这个过程,造成较低的可用性。

为了解决这个问题,节点在认为当前存在Leader时会忽略RequestVote RPC。具体来说,如果节点在选举超时时间内收到过来自当前Leader的请求,它将拒绝为RequestVote RPC投票,而且也不会更新自己的term。这不影响正常的选举,每个节点在开始选举之前都至少等待选举超时时间。但是,这个约束有助于避免被移除的服务器干扰整个集群:如果Leader能够向集群发送心跳,则不会因更大的term而被降级。

7. 日志压缩

在正常运行期间,Raft日志会随客户端请求的增多而增长。但在实际系统中,日志不能无限制地增长。随着日志的增长,它占用的空间也会增加,并且日志重演所花费的时间也会增加。如果没有某种机制来丢弃日志中积累的过时信息,这终将造成可用性问题。

快照是最简单的压缩方法。在快照中,整个当前系统状态被写入到一个存储于稳定介质的快照文件,然后在该点前的所有日志就都被废弃了。快照方案也被Chubby和ZooKeeper所采用,本节其余部分将描其在Raft中的应用。

译者注:快照是一种checkpoint思想,不仅用在共识算法领域,在整个软件开发领域都有广泛的应用,比如Redis的RDB、MySQL的checkpoint等等。

增量式压缩方法,如日志清理[36]和LSM树[30,5]也是可行的方案。它们一次只操作数据的一部分,因此随着时间的推移,它们会更均匀地分散压缩负载。这些方案首先选择一个包含大量已删除和覆盖的对象的数据区域,然后重写该区域中活动对象的数据,并释放该区域的空间。这比快照需要更多的额外机制,进而也更复杂性,因为快照总是对整个数据集进行操作以简化问题。虽然需要修改Raft来实现日志请求,但状态机可以使用与快照相同的接口实现LSM树。

图12展示了Raft 中快照的基本思路。每个节点都可以独立地创建快照,其中只包含其日志中已提交的部分条目。状态机的大部分工作是将其当前状态写入快照。Raft还在快照中包含少量元数据:lastIncludedIndex是被快照替换掉的最后一个日志条目(即状态机应用的最后一个条目)的index,lastIncludedTerm是该条目的term。这些信息保留下来的目的是支持快照后第一个AppendEntries RPC的一致性检查,因为过程中需要上一个日志条目的term和index。为了支持集群成员变更(见第6节),快照还包括到lastIncludedIndex为止的最新配置。节点一旦完成了快照的写入,它就可以删除所有index小于等于lastIncludedIndex的日志条目,以及所有老版本快照。
图12

图12:节点用快照替换掉日志中已经提交的部分(index 1到5),其中只包含了当前状态数据(对应图中的x和y)。这个快照中的last included index和last included term为index=6的日志条目的写入提供支持,用于定位其前驱条目。

尽管节点通常独立地创建快照,但Leader在某些场景下必须向落后的Follower发送快照。 比如下一个需要发送给落后Follower的日志条目已被删除(因为Leader完成了快照)。 幸运的是,在正常运维过程中不太可能发生这种情况:一个能够跟上进度的Follower一般已经包含了该条目;该过程一般发生在Follower因异常因素而性能低下,或新节点刚加入集群的场景下。处理方式是Leader通过网络向对应节点发送快照。

Leader使用一个称为InstallSnapshot的新类型RPC将快照发送给过于落后的Follower(见图13)。当一个Follower通过该RPC接收到一个快照时,它必须判断如何处理本地现有的日志条目。通常情况下,快照会包含接收方日志中不存在的新信息。在这种情况下,Follower会丢弃其整个日志,都用快照代替,日志中也可能包含未提交且与快照冲突的条目。否则,由于重传或错误,追随者接收到对应其日志前缀的快照,那么快照对应的日志条目将被删除,但位于快照之后的条目仍然有效并且必须保留。
图13

图13:InstallSnapshot RPC的总结。快照被分割为数据块来进行传输;每个块的传输都可看做对Follower的心跳,会重置其选举计时器。

其实这种快照方法偏离了Raft的强Leader原则,因为Follower可以在无需Leader信息的前提下进行快照。然而,我们认为这个原则的违背是有道理的。虽然拥有一个Leader有助于避免在达成共识过程中产生冲突,但当进行快照时已经达成了共识,因此没有冲突需要Leader去协调。数据仍然只从Leader流向Follower,只是Follower也有了重组自己数据的能力。

我们考虑了另一种基于Leader的快照方案,其中只有Leader可以创建快照,然后将其发送给每个Follower。然而,这有两个缺点。首先,向每个Follower发送快照会浪费网络带宽并拖慢快照的过程。每个Follower已经拥有生成自己快照所需的信息,而且对于节点来说,从本地状态生成快照通常比通过网络传输和接收的成本低得多。其次,Leader的实现会更复杂。例如,Leader需要在复制新的日志条目到Follower的过程中并行地向其发送快照,以免阻塞新的客户端请求。

还有另外两个影响快照性能的因素。首先,节点必须决定何时进行快照。如果节点频繁地创建快照,那么它会浪费磁盘带宽和电力;如果节点很少创建快照,那么它的存储容量可能会耗尽,并且在重启时重放日志所需的时间也会增加。一个简单的策略是在日志达到固定大小(以字节为单位)时进行快照。如果这个大小被设置得远大于预期的快照大小,那么快照所花费的磁盘带宽开销就会很小。

第二个性能问题是写快照可能需要很长时间,我们不希望这对正常操作引入延迟。解决办法是使用CopyOnWrite技术,这样可以接受新的更新而不影响已写入的快照。例如,基于函数式数据结构构建的状态机天然地支持这一点。或者,操作系统对CopyOnWrite的支持(例如 Linux 中的 fork)可用于在内存中创建整个状态机的快照(我们的实现方案采用了这种方法)。

8. 客户端交互

本节描述了客户端如何与Raft交互,包括客户端如何找到集群中的Leader以及Raft如何支持可串行语义[10]。这些问题适用于所有基于共识的系统,而Raft的解决方案与其他也系统很相似。

Raft客户端将所有请求发送到Leader。当客户端启动时会随机连接到一个节点。如果客户端第一次选择的不是Leader,那么该节点将会拒绝客户端的请求,并返回所感知到的最新Leader信息(AppendEntries RPC包括Leader的网络地址)。如果Leader崩溃,客户端请求会超时;然后客户端会再次尝试连接到随机的节点。

我们对Raft的目标是实现可串行化的语义(每个操作看起来像在调用和响应之间的某个时间点执行一次,且仅执行了一次)。然而,如前所述,Raft可以多次执行同一个命令:例如,如果Leader在提交日志条目但尚未响应客户端时崩溃,客户端向新的Leader重试该命令,导致其第二次被执行。解决方案是让客户端为每个命令分配唯一的序列号。然后,状态机跟踪每个客户端处理的最新序列号以及相关联的响应。如果节点接收到一个已经执行过的序号的命令,其会立即对其作出响应,而不会重新执行该请求。

只读操作在不写入任何日志的前提下被处理。然而,如果没有额外措施,只读操作也可能会返回过时的数据,因为对请求作出响应的Leader可能已经被一个它未感知的新Leader所取代。可串行化读取必须不能返回过时的数据,而Raft需要两个额外的预防措施来在不使用日志的情况下保证这一点。首先,Leader必须知道哪些条目已提交。Leader完整性保证了Leader拥有所有已提交的条目,但在其term开始时,它可能还没有这些信息。为了找到这些条目,它需要从它的term开始提交一条条目。Raft通过让每个Leader在其term开始时向日志中提交一个空的条目来解决此问题。第二,Leader必须在处理只读请求之前检查它是否已被新Leader代替(如有果新Leader被选举出来,则本节点信息可能已过时)。Raft通过在响应只读请求之前让Leader与集群中的多数节点进行心跳消息交换来处解决问题。或者,Leader可以依赖心跳机制提供一种租约[9]形式,但其安全性依赖于时间(其假设时钟偏差是受限制的)。

9. 实现与评估

我们在RAMCloud[33]中实现了Raft,作为配置存储状态机的一部分,协助RAMCloud进行故障恢复。该Raft的实现大约包含2000 行C++代码,其中不包括测试、注释或空行。 源代码可以自由获取[23] 。此外,有大约25个独立的第三方开源的Raft实现[34] ;这些实现基于本文的草稿,并处于不同开发阶段。另外,多个公司公司也在部署基于Raft的系统[34] 。

本节其余部分在可理解性、正确性和性能三个标准来评估Raft。

9.1 可理解性

为了衡量Raft和Paxos在可理解性方面的优劣,我们在斯坦福大学的高级操作系统课程和加州大学伯克利分校的分布式计算课程的高年级本科生和研究生之间进行了实验研究。我们录制了Raft和Paxos的视频可成,并设计了相应的测验。Raft可成涵盖了本文中除日志压缩之外的内容;Paxos可成涵盖了创建一个等效的复制状态机的足够的材料,包括Single-Paxos、Multi-Paxos、配置变更以及实际系统需要的一些优化(如Leader选举)。测验考察了学生对算法的基本理解,并要求他们考虑边界场景。每个学生观看一段视频,完成相应的测验,然后观看第二段视频并完成第二个测验。大约一半的学生先做Paxos部分,另一半先做Raft部分,用以将个体表能力差异和从第一部分学习中获得的经验纳入考虑范围内(译者注:第一段的学习经验加速了第二段学习)。我们比较了参与者在每次测验中的得分,以确定参与者是否对Raft的理解更好。

我们努力让Paxos和Raft的比较尽可能公平。实验以两种方式向Paxos倾斜:43名参与者中有15人反馈称他们之前使用过Paxos;另外,Paxos教学视频比Raft视频长14%。如表 1所述,我们已采取措施来消除潜在的误差来源。我们所有材料都可供审查[28, 31]。
表1

表1:学习中的关注点(尽可能偏向Paxos)、关注点对应的具体措施以及额外可供审查的材料

平均而言,参与者在Raft测试中的得分比Paxos高4.9分(满分为60,Raft的平均得分为 25.7,Paxos的平均得分为20.8);图14展示了他们的个人分数。配对t检验表明,在 95%的置信度下,Raft分数的真实分布的平均值比Paxos 至少高2.5分。
图14

图14:比对43名参与者在Raft和Paxos中个人表现的散点图。对角线上半部分的33个点代表了Raft分数大于Paxos分数的参与者。

我们还创建了一个线性回归模型,根据三个因素预测新学生的测验分数:他们参加了哪个测验、他们之前对Paxos的经验高低以及他们先学习哪个算法。模型预测测验的选择使Raft产生了的12.5分的优势。这比观察到的4.9分要高得多,因为许多学生之前有Paxos经验,这对Paxos 帮助很大,而对Raft的帮助较少。有趣的是,模型还预测已经通过了Paxos测验的人在Raft上得分会低6.3 分;虽然我们不知道为什么,但这个现象非常明显地体现在统计结果上。

我们还对测验后的参与者进行了调查,为了了解他们认为哪种算法更容易实现或解释;结果如图15所示。绝大多数参与者表示Raft更容易实现和解释(每个问题都问了41名参与者中的33名)。然而,这些主观的感受可能不如参与者的测验分数可靠,而且参与者之前可能就已受到"Raft更易理解"的看法影响。
图15

图15:参与者被询问他们觉得哪一个在实现正确、有效的功能系统过程中更加容易、哪一个在和CS学生解释起来更加容易。

有关Raft用户研究的详细讨论,请参阅 [31]。

9.2 正确性

我们为第5节中描述的一致性机制给出了了一个形式化规范和安全证明。该形式化规范[31]使用TLC语言[17]完整而精确地表达了图2中总结的信息。它大约有400行,用作证明的题目。它本身对于任何实现Raft的人都有用。我们使用TLC证明系统[7]机械地证明了日志完整性。然而,这个证明依赖于尚未经过机械验证的不变式(例如,我们没有证明规范的类型安全性)。此外,我们还写了一个状态机安全性的非正式证明[31],其中包含了状态机安全性属性的完整内容(它只依赖于规范)并且与规范相对应(大约3500字的长度)。

9.3 性能

Raft的性能与其他共识算法(如 Paxos)类似。性能评估中最重要的场景是Leader对日志条目的复制。Raft通过最少的消息数来提升这一场景的效率(从Leader到集群一半节点的单轮通信)。不过Raft的性能还可以进一步提高。例如,可以通过批量处理和管道操作轻松地获得更高的吞吐量和更低的延迟。过去的文献已经为其他算法提出了各种优化;而其中许多方案也适用于Raft,但我们将此留给未来去解决。

我们使用我们的Raft实现来评估其 Leader选举算法的性能,并回答两个问题。首先,选举过程是否能快速收敛?其次,在Leader崩溃后,最小停机时间是多少?

为了评估这方面的表现,我们多次使一组由五个服务器组成的集群中的Leader崩溃,并记录从崩溃检测到选举出新Leader所花费的时间(见图16)。为了生成最差场景,每个试验中的节点具有不同的日志长度,因此一些Candidate无法成为Leader。此外,为了触发投票分裂,我们的测试脚本在终止Leader进程之前会触发其同步发送一个轮心跳RPC广播(这类似于Leader在崩溃之前复制新的日志条目)。统一在Leader在心跳间隔内随机触发其崩溃;在所有测试中,心跳间隔是选举超时时间的一半。因此,可能的最小停机时间大约是最短选举超时时间的一半。
图16

图16:检测和替换Leader的时间分布。上面图表中各试验中选举超时时间的随机部分占比不同,下面图表逐步放大最小选举超时时间。每条线表示1000次试验(150ms-150ms除外,其进行了100次试验),并且对应一种选举超时时间的选择;例如,“150-155ms”意味着选举超时时间统一在150ms和155ms之间随机选取。测量结果表明,在5节点的集群中,广播时间约为15ms;9节点集群的测量结果类似。

图16中的顶部图表显示,选举超时的一小部分随机性足以避免选举中的分裂投票。在没有随机性的情况下,在我们的测试中,由于触发了很多次分裂的投票,Leader选举始终需要超过10s的时间。仅添加5ms的随机性就将停机时间中位数降为287ms,效果显著。使用更多的随机性可以改善最坏场景下的表现:在50ms的随机性下,最坏情况下停机时间为513ms(超过1000次试验)。

图16中底部的图表显示,减少选举超时时间可以降低停机时间。当选举超时为12-24ms时,平均只需要35ms就可以选出一个Leader(最长试验时间为152ms)。然而,进一步降低超时会违反Raft的时间要求:在其他节点开始新选举之前,Leader很难完成心跳信息的广播。这可能会引发不必要的Leader变更并降低整体系统的可用性。我们建议使用保守的选举超时时间,例如150-300ms;这样的超时时间不太可能导致不必要的Leader选举,并且仍然能够提供良好的可用性。

10. 相关工作

关于共识算法有许多出版物,其中大致有如下几类:

  • Lamport的Paxos 原始描述[15],以及试图更清楚地解释该算法[16, 20, 21];

  • Paxos算法的改进版本,补充了缺失的细节,并提供更好的实现基础[26, 39, 13];

  • 实现了共识算法的系统,如Chubby[2, 4]、ZooKeeper[11, 12]和Spanner[6];虽然都Chubby和Spanner声称基于Paxos,但其算法尚未公开;ZooKeeper的算法已经公开,但与Paxos之间有很大不同;

  • 适用于Paxos的性能优化[18, 19, 3, 25, 1, 27];

  • Okidisk和Liskov的Viewstamped Replication (VR),这是与Paxos同时开发的另一种共识方法;最初的描述[29]与分布式事务协议交织在一起,不过最近的更新[22]将核心共识协议单独提出来了;VR使用基于Leader的方法,与Raft具有很多相似之处 ;

Raft和Paxos之间最大的区别在于Raft的强Leader模式:Raft将Leader选举作为一致性协议的重要组成部分,并在领导者中实现尽可能多的功能。这种方法产生了一个更简单、更容易被理解的算法。例如,在Paxos中,Leader选举与基本共识协议关联度不高:其仅用于性能优化,达成共识的过程并不需要Leader。然而,这需要额外的机制:Paxos在基本共识算法和Leader选举中都涉及了两阶段协议。相比之下,Raft直接将Leader选举纳入共识算法并将其作为两个阶段共识的第一阶段。这比Paxos需要更少的额外机制。

与Raft一样,VR和ZooKeeper也是基于Leader的模式,因此许多Raft相对于Paxos的优点在这两个算法中同时存在。然而,由于Raft削减了非Leader节点的功能,所以比VR和ZooKeeper少了一些额外机制。例如,在Raft中,日志条目仅单向传输:在 AppendEntries RPC中从Leader向外流动。而在VR中,日志条目的流动是双向的(在选举过程中,Leader也可以接收日志条目);这引入了额外的机制和复杂性。ZooKeeper 的公开版本也涉及以Leader为起点或终点发送日志条目,但实现上显然更接近于Raft [35]。

在我们所了解的基于共识的日志复制算法中,Raft涉及的消息类型最少。例如,我们分析了VR和ZooKeeper在基本共识和成员变更时使用的消息类型数量(排除日志压缩和客户端交互,因为这些过程几乎与算法无关)。VR和 ZooKeeper分别定义了10种不同的消息类型,而Raft只有4种消息类型(两个远程过程调用请求及其响应)。Raft的消息比其他算法稍显密集,但总体上更简单。此外,VR和ZooKeeper在Leader变更期间会传输整个日志;需要额外的消息类型来优化这些机制使其更加实用。

Raft的强Leader方法简化了算法,但这也将一些性能优化方式排除在外了。例如,在某些情况下,无主Paxos(EPaxos)在某些条件下可以实现更高的性能,无需Leader[27]。EPaxos利用了状态机命令的可交换性。只要其他指令也在被并行地提出,任何节点都可以仅通过一轮通信来提交一个命令。然而,如果同一时间提出的所有命令之间不可交换,则EPaxos需要额外的一轮通信。由于任何节点都可能提交命令,因此 EPaxos在节点之间实现了很好的负载平衡,并且在广域网环境中能够实现比Raft更低的延迟。但是,它在Paxos基础上显著增加了复杂性。

在其他研究成果中,已经提出或实现了几种不同的集群成员变更方法,包括Lamport最初的描述[15]、VR[22]和SMART[24]。我们在Raft选择联合共识的方法,因为这复用了共识协议的其余部分,所需的额外机制很少。Lamport基于α的方法对Raft而言不是一个可选项,因为它假设可以在没有Leader的情况下达成共识。与VR和SMART相比,Raft的配置变更算法的优势在于成员变更过程不限制的正常请求;相反,VR在配置更改期间会停止所有正常处理,而SMART对未决请求数量施加类似于α的限制。同时,Raft的方法也比VR和SMART需要更少的额外机制。

11. 结论

算法通常以正确性、效率或简洁性为主要目标而进行设计。虽然这些都是值得追求的目标,但我们认为可理解性也同样重要。在开发人员将算法转化为实际实现之前,这些都不是可以达到的目标,而实际实现必然在已发表的算法基础上有所不同和扩展。除非开发人员对算法有深入的理解,并能为其创建直觉,否则他们很难在其实现中保留算法的优势特性。

在这篇论文中,我们讨论了分布式共识的问题,一个广为接受但难以理解的Paxos算法 已经困扰学生和开发人员多年。我们设计了一个新的算法,Raft,并证明它比Paxos更容易理解。我们还认为,Raft 为系统构建提供了更好的基础。将可理解性作为主要设计目标改变了我们对Raft设计的方法;随着设计的发展,我们发现自己多次使用了一些方法,例如问题分解和状态空间简化。这些方法不仅提高了Raft的可理解性,而且使我们更容易相信它的正确性。

12. 致谢

用户研究过程中,Ali Ghodsi、David Mazières、伯克利CS 294-91学生以及斯坦福大学 CS 240学生的支持至关重要。Scott Klemmer帮助我们设计了用户研究,Nelson Ray在统计分析方面为我们提供了建议。用户研究中关于Paxos的幻灯片大量借鉴了由 Lorenzo Alvisi创作的原始版本。特别感谢David Mazières和Ezra Hoch发现了Raft中的一些微妙bug。许多人为论文和用户研究材料提供了有益的反馈,包括Ed Bugnion、Michael Chan、Hugues Evrard、Daniel Gifinn、Arjun Gopalan、Jon Howell、Vimalkumar Jeyakumar、Ankita Kejriwal、Aleksandar Kracun、Amit Levy、Joel Martin、Satoshi Matsushita、Oleg Pesok、David Ramos、Robbert van Renesse、Mendel Rosenblum、Nicolás Schiper、Deian Stefan、Andrew Stone、Ryan Stutsman、David Terei、Stephen Yang、Matei Zaharia以及24名匿名会议评审者(含重复),尤其是我们的领导Eddie Kohler。Werner Vogels在推特上发布了一个指向早期草稿的链接,为Raft提供了巨大的曝光率。本次研究得到了Gigascale Systems Research Center和Multiscale Systems Center的支持,这是Center Research Program六个研究中心中的两个(这是半导体研究中心的一个项目,由STARnet资助;STARnet是由马里兰州DARPA赞助的半导体研究中心的一个项目,由国家科学基金会NSF-0963859资助,并且来自Facebook、Google、Mellanox、NEC、NetApp、SAP和Samsung的拨款)。Diego Ongaro获得了Junglee公司斯坦福大学研究生奖学金的资助。

参考文献

[1] BOLOSKY, W. J., BRADSHAW, D., HAAGENS, R. B., KUSTERS, N. P., AND LI, P. Paxos replicated state machines as the basis of a high-performance data store. In Proc. NSDI’11, USENIX Conference on Networked Systems Design and Implementation (2011), USENIX, pp. 141–154.
[2] BURROWS, M. The Chubby lock service for looselycoupled distributed systems. In Proc. OSDI’06, Symposium on Operating Systems Design and Implementation (2006), USENIX, pp. 335–350.
[3] CAMARGOS, L. J., SCHMIDT, R. M., AND PEDONE, F. Multicoordinated Paxos. In Proc. PODC’07, ACM Symposium on Principles of Distributed Computing (2007), ACM, pp. 316–317.
[4] CHANDRA, T. D., GRIESEMER, R., AND REDSTONE, J. Paxos made live: an engineering perspective. In Proc. PODC’07, ACM Symposium on Principles of Distributed Computing (2007), ACM, pp. 398–407.
[5] CHANG, F., DEAN, J., GHEMAWAT, S., HSIEH, W. C., WALLACH, D. A., BURROWS, M., CHANDRA, T., FIKES, A., AND GRUBER, R. E. Bigtable: a distributed storage system for structured data. In Proc. OSDI’06, USENIX Symposium on Operating Systems Design and Implementation (2006), USENIX, pp. 205–218.
[6] CORBETT, J. C., DEAN, J., EPSTEIN, M., FIKES, A., FROST, C., FURMAN, J. J., GHEMAWAT, S., GUBAREV, A., HEISER, C., HOCHSCHILD, P., HSIEH, W., KANTHAK, S., KOGAN, E., LI, H., LLOYD, A., MELNIK, S., MWAURA, D., NAGLE, D., QUINLAN, S., RAO, R., ROLIG, L., SAITO, Y., SZYMANIAK, M., TAYLOR, C., WANG, R., AND WOODFORD, D. Spanner: Google’s globally-distributed database. In Proc. OSDI’12, USENIX Conference on Operating Systems Design and Implementation (2012), USENIX, pp. 251–264.
[7] COUSINEAU, D., DOLIGEZ, D., LAMPORT, L., MERZ, S., RICKETTS, D., AND VANZETTO, H. TLA+ proofs. In Proc. FM’12, Symposium on Formal Methods(2012), D. Giannakopoulou and D. M´ery, Eds., vol. 7436 of Lec-ture Notes in Computer Science, Springer, pp. 147–154.
[8] GHEMAWAT, S., GOBIOFF, H., AND LEUNG, S.-T. The Google file system. In Proc. SOSP’03, ACM Symposium on Operating Systems Principles(2003), ACM, pp. 29–43.
[9] GRAY, C., AND CHERITON, D. Leases: An efficient fault-tolerant mechanism for distributed file cache consistency. In Proceedings of the 12th ACM Ssymposium on Operating Systems Principles(1989), pp. 202–210.
[10] HERLIHY, M. P., AND WING, J. M. Linearizability: a correctness condition for concurrent objects. ACM Trans-actions on Programming Languages and Systems 12(July1990), 463–492.
[11] HUNT, P., KONAR, M., JUNQUEIRA, F. P., AND REED, B. ZooKeeper: wait-free coordination for internet-scale systems. In Proc ATC’10, USENIX Annual Technical Con-ference(2010), USENIX, pp. 145–158.
[12] JUNQUEIRA, F. P., REED, B. C., AND SERAFINI, M. Zab: High-performance broadcast for primary-backup sys-tems. In Proc. DSN’11, IEEE/IFIP Int’l Conf. on Depend-able Systems&Networks(2011), IEEE Computer Society, pp. 245–256.
[13] KIRSCH, J., AND AMIR, Y. Paxos for system builders. Tech. Rep. CNDS-2008-2, Johns Hopkins University,2008.
[14] LAMPORT, L. Time, clocks, and the ordering of events in a distributed system. Commununications of the ACM 21, 7(July 1978), 558–565.
[15] LAMPORT, L. The part-time parliament. ACM Transac-tions on Computer Systems 16, 2(May 1998), 133–169.
[16] LAMPORT, L. Paxos made simple. ACM SIGACT News32, 4(Dec. 2001), 18–25.
[17] LAMPORT, L. Specifying Systems, The TLA+ Language and Tools for Hardware and Software Engineers. Addison-Wesley, 2002.
[18] LAMPORT, L. Generalized consensus and Paxos. Tech. Rep. MSR-TR-2005-33, Microsoft Research, 2005.
[19] LAMPORT, L. Fast paxos. Distributed Computing 19, 2(2006), 79–103.
[20] LAMPSON, B. W. How to build a highly available system using consensus. In Distributed Algorithms, O. Baboaglu and K. Marzullo, Eds. Springer-Verlag, 1996, pp. 1–17.
[21] LAMPSON, B. W. The ABCD’s of Paxos. In Proc. PODC’01, ACM Symposium on Principles of Distributed Computing(2001), ACM, pp. 13–13.
[22] LISKOV, B., AND COWLING, J. Viewstamped replica-tion revisited. Tech. Rep. MIT-CSAIL-TR-2012-021, MIT, July 2012.
[23] LogCabin source code. http://github.com/logcabin/logcabin.
[24] LORCH, J. R., ADYA, A., BOLOSKY, W. J., CHAIKEN, R., DOUCEUR, J. R., AND HOWELL, J. The SMART way to migrate replicated stateful services. In Proc. Eu-roSys’06, ACM SIGOPS/EuroSys European Conference on Computer Systems(2006), ACM, pp. 103–115.
[25] MAO, Y., JUNQUEIRA, F. P., AND MARZULLO, K. Mencius: building efficient replicated state machines for WANs. In Proc. OSDI’08, USENIX Conference on Operating Systems Design and Implementation(2008), USENIX, pp. 369–384.
[26] MAZIE RES, D. Paxos made practical. http://www.scs.stanford.edu/˜dm/home/papers/paxos.pdf, Jan. 2007.
[27] MORARU, I., ANDERSEN, D. G., AND KAMINSKY, M. There is more consensus in egalitarian parliaments. In Proc. SOSP’13, ACM Symposium on Operating System Principles(2013), ACM.
[28] Raft user study. http://ramcloud.stanford. edu/˜ongaro/userstudy/.
[29] OKI, B. M., AND LISKOV, B. H. Viewstamped replication: A new primary copy method to support highly-available distributed systems. In Proc. PODC’88, ACM Symposium on Principles of Distributed Computing(1988), ACM, pp. 8–17.
[30] O’NEIL, P., CHENG, E., GAWLICK, D., AND ONEIL, E. The log-structured merge-tree(LSM-tree). Acta Informat-ica 33, 4(1996), 351–385.
[31] ONGARO, D. Consensus: Bridging Theory and Practice. PhD thesis, Stanford University, 2014(work in progress).http://ramcloud.stanford.edu/˜ongaro/thesis.pdf.
[32] ONGARO, D., AND OUSTERHOUT, J. In search of an understandable consensus algorithm. In Proc ATC’14, USENIX Annual Technical Conference(2014), USENIX.
[33] OUSTERHOUT, J., AGRAWAL, P., ERICKSON, D., KOZYRAKIS, C., LEVERICH, J., MAZIE`RES, D., MI-TRA, S., NARAYANAN, A., ONGARO, D., PARULKAR, G., ROSENBLUM, M., RUMBLE, S. M., STRATMANN, E., AND STUTSMAN, R. The case for RAMCloud. Com-munications of the ACM 54(July 2011), 121–130.
[34] Raft consensus algorithm website. http://raftconsensus.github.io.
[35] REED, B. Personal communications, May 17, 2013.
[36] ROSENBLUM, M., AND OUSTERHOUT, J. K. The design and implementation of a log-structured file system. ACM Trans. Comput. Syst. 10(February 1992), 26–52.
[37] SCHNEIDER, F. B. Implementing fault-tolerant services using the state machine approach: a tutorial. ACM Com-puting Surveys 22, 4(Dec. 1990), 299–319.
[38] SHVACHKO, K., KUANG, H., RADIA, S., AND CHANSLER, R. The Hadoop distributed file system. In Proc. MSST’10, Symposium on Mass Storage Sys-tems and Technologies(2010), IEEE Computer Society, pp. 1–10.
[39] VAN RENESSE, R. Paxos made moderately complex. Tech. rep., Cornell University, 2012.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值