摘要算法
Raft 是一种为了管理复制日志的一致性算法。它提供了和 Paxos 算法相同的功能和性能,可是它的算法结构和 Paxos 不一样,使得 Raft 算法更加容易理解而且更容易构建实际的系统。为了提高可理解性,Raft 将一致性算法分解成了几个关键模块,例如领导人选举、日志复制和安全性。同时它经过实施一个更强的一致性来减小须要考虑的状态的数量。从一个用户研究的结果能够证实,对于学生而言,Raft 算法比 Paxos 算法更加容易学习。Raft 算法还包括一个新的机制来容许集群成员的动态改变,它利用重叠的大多数来保证安全性。安全
一致性算法容许一组机器像一个总体同样工做,即便其中一些机器出现故障也可以继续工做下去。正由于如此,一致性算法在构建可信赖的大规模软件系统中扮演着重要的角色。在过去的 10 年里,Paxos 算法统治着一致性算法这一领域:绝大多数的实现都是基于 Paxos 或者受其影响。同时 Paxos 也成为了教学领域里讲解一致性问题时的示例。性能优化
可是不幸的是,尽管有不少工做都在尝试下降它的复杂性,可是 Paxos 算法依然十分难以理解。而且,Paxos 自身的算法结构须要进行大幅的修改才可以应用到实际的系统中。这些都致使了工业界和学术界都对 Paxos 算法感到十分头疼。服务器
和 Paxos 算法进行过努力以后,咱们开始寻找一种新的一致性算法,能够为构建实际的系统和教学提供更好的基础。咱们的作法是不寻常的,咱们的首要目标是可理解性:咱们是否能够在实际系统中定义一个一致性算法,而且可以比 Paxos 算法以一种更加容易的方式来学习。此外,咱们但愿该算法方便系统构建者的直觉的发展。不只一个算法可以工做很重要,并且可以显而易见的知道为何能工做也很重要。网络
Raft 一致性算法就是这些工做的结果。在设计 Raft 算法的时候,咱们使用一些特别的技巧来提高它的可理解性,包括算法分解(Raft 主要被分红了领导人选举,日志复制和安全三个模块)和减小状态机的状态(相对于 Paxos,Raft 减小了非肯定性和服务器互相处于非一致性的方式)。一份针对两所大学 43 个学生的研究代表 Raft 明显比 Paxos 算法更加容易理解。在这些学生同时学习了这两种算法以后,和 Paxos 比起来,其中 33 个学生可以回答有关于 Raft 的问题。数据结构
Raft 算法在许多方面和现有的一致性算法都很类似(主要是 Oki 和 Liskov 的 Viewstamped Replication),可是它也有一些独特的特性:并发
咱们相信,Raft 算法不论出于教学目的仍是做为实践项目的基础都是要比 Paxos 或者其余一致性算法要优异的。它比其余算法更加简单,更加容易理解;它的算法描述足以实现一个现实的系统;它有好多开源的实现而且在不少公司里使用;它的安全性已经被证实;它的效率和其余算法比起来也不相上下。负载均衡
接下来,这篇论文会介绍如下内容:复制状态机问题(第 2 节),讨论 Paxos 的优势和缺点(第 3 节),讨论咱们为了可理解性而采起的方法(第 4 节),阐述 Raft 一致性算法(第 5-8 节),评价 Raft 算法(第 9 节),以及一些相关的工做(第 10 节)。分布式
一致性算法是从复制状态机的背景下提出的(参考英文原文引用37)。在这种方法中,一组服务器上的状态机产生相同状态的副本,而且在一些机器宕掉的状况下也能够继续运行。复制状态机在分布式系统中被用于解决不少容错的问题。例如,大规模的系统中一般都有一个集群领导者,像 GFS、HDFS 和 RAMCloud,典型应用就是一个独立的的复制状态机去管理领导选举和存储配置信息而且在领导人宕机的状况下也要存活下来。好比 Chubby 和 ZooKeeper。函数
图 1 :复制状态机的结构。一致性算法管理着来自客户端指令的复制日志。状态机从日志中处理相同顺序的相同指令,因此产生的结果也是相同的。
复制状态机一般都是基于复制日志实现的,如图 1。每个服务器存储一个包含一系列指令的日志,而且按照日志的顺序进行执行。每个日志都按照相同的顺序包含相同的指令,因此每个服务器都执行相同的指令序列。由于每一个状态机都是肯定的,每一次执行操做都产生相同的状态和一样的序列。
保证复制日志相同就是一致性算法的工做了。在一台服务器上,一致性模块接收客户端发送来的指令而后增长到本身的日志中去。它和其余服务器上的一致性模块进行通讯来保证每个服务器上的日志最终都以相同的顺序包含相同的请求,尽管有些服务器会宕机。一旦指令被正确的复制,每个服务器的状态机按照日志顺序处理他们,而后输出结果被返回给客户端。所以,服务器集群看起来造成一个高可靠的状态机。
实际系统中使用的一致性算法一般含有如下特性:
在过去的 10 年里,Leslie Lamport 的 Paxos 算法几乎已经成为一致性的代名词:Paxos 是在课程教学中最常用的算法,同时也是大多数一致性算法实现的起点。Paxos 首先定义了一个可以达成单一决策一致的协议,好比单条的复制日志项。咱们把这一子集叫作单决策 Paxos。而后经过组合多个 Paxos 协议的实例来促进一系列决策的达成。Paxos 保证安全性和活性,同时也支持集群成员关系的变动。Paxos 的正确性已经被证实,在一般状况下也很高效。
不幸的是,Paxos 有两个明显的缺点。第一个缺点是 Paxos 算法特别的难以理解。完整的解释是出了名的不透明;经过极大的努力以后,也只有少数人成功理解了这个算法。所以,有了几回用更简单的术语来解释 Paxos 的尝试。尽管这些解释都只关注了单决策的子集问题,但依然很具备挑战性。在 2012 年 NSDI 的会议中的一次调查显示,不多有人对 Paxos 算法感到满意,甚至在经验老道的研究者中也是如此。咱们本身也尝试去理解 Paxos;咱们一直没能理解 Paxos 直到咱们读了不少对 Paxos 的简化解释而且设计了咱们本身的算法以后,这一过程花了近一年时间。
咱们假设 Paxos 的不透明性来自它选择单决策问题做为它的基础。单决策 Paxos 是晦涩微妙的,它被划分红了两种没有简单直观解释和没法独立理解的情景。所以,这致使了很难创建起直观的感觉为何单决策 Paxos 算法可以工做。构成多决策 Paxos 增长了不少错综复杂的规则。咱们相信,在多决策上达成一致性的问题(一份日志而不是单一的日志记录)可以被分解成其余的方式而且更加直接和明显。
Paxos算法的第二个问题就是它没有提供一个足够好的用来构建一个现实系统的基础。一个缘由是尚未一种被普遍认同的多决策问题的算法。Lamport 的描述基本上都是关于单决策 Paxos 的;他简要描述了实施多决策 Paxos 的方法,可是缺少不少细节。固然也有不少具体化 Paxos 的尝试,可是他们都互相不同,和 Paxos 的概述也不一样。例如 Chubby 这样的系统实现了一个相似于 Paxos 的算法,可是大多数的细节并无被公开。
并且,Paxos 算法的结构也不是十分易于构建实践的系统;单决策分解也会产生其余的结果。例如,独立的选择一组日志条目而后合并成一个序列化的日志并无带来太多的好处,仅仅增长了很多复杂性。围绕着日志来设计一个系统是更加简单高效的;新日志条目以严格限制的顺序增添到日志中去。另外一个问题是,Paxos 使用了一种对等的点对点的方式做为它的核心(尽管它最终提议了一种弱领导人的方法来优化性能)。在只有一个决策会被制定的简化世界中是颇有意义的,可是不多有现实的系统使用这种方式。若是有一系列的决策须要被制定,首先选择一个领导人,而后让他去协调全部的决议,会更加简单快速。
所以,实际的系统中不多有和 Paxos 类似的实践。每一种实现都是从 Paxos 开始研究,而后发现不少实现上的难题,再而后开发了一种和 Paxos 明显不同的结构。这样是很是费时和容易出错的,而且理解 Paxos 的难度使得这个问题更加糟糕。Paxos 算法在理论上被证实是正确可行的,可是现实的系统和 Paxos 差异是如此的大,以致于这些证实没有什么太大的价值。下面来自 Chubby 实现很是典型:
在Paxos算法描述和实现现实系统中间有着巨大的鸿沟。最终的系统创建在一种没有通过证实的算法之上。
因为以上问题,咱们认为 Paxos 算法既没有提供一个良好的基础给实践的系统,也没有给教学很好的帮助。基于一致性问题在大规模软件系统中的重要性,咱们决定看看咱们是否能够设计一个拥有更好特性的替代 Paxos 的一致性算法。Raft算法就是此次实验的结果。
设计 Raft 算法咱们有几个初衷:它必须提供一个完整的实际的系统实现基础,这样才能大大减小开发者的工做;它必须在任何状况下都是安全的而且在大多数的状况下都是可用的;而且它的大部分操做必须是高效的。可是咱们最重要也是最大的挑战是可理解性。它必须保证对于广泛的人群均可以十分容易的去理解。另外,它必须可以让人造成直观的认识,这样系统的构建者才可以在现实中进行必然的扩展。
在设计 Raft 算法的时候,有不少的点须要咱们在各类备选方案中进行选择。在这种状况下,咱们评估备选方案基于可理解性原则:解释各个备选方案有多大的难度(例如,Raft 的状态空间有多复杂,是否有微妙的暗示)?对于一个读者而言,彻底理解这个方案和暗示是否容易?
咱们意识到对这种可理解性分析上具备高度的主观性;尽管如此,咱们使用了两种一般适用的技术来解决这个问题。第一个技术就是众所周知的问题分解:只要有可能,咱们就将问题分解成几个相对独立的,可被解决的、可解释的和可理解的子问题。例如,Raft 算法被咱们分红领导人选举,日志复制,安全性和角色改变几个部分。
咱们使用的第二个方法是经过减小状态的数量来简化须要考虑的状态空间,使得系统更加连贯而且在可能的时候消除不肯定性。特别的,全部的日志是不容许有空洞的,而且 Raft 限制了日志之间变成不一致状态的可能。尽管在大多数状况下咱们都试图去消除不肯定性,可是也有一些状况下不肯定性能够提高可理解性。尤为是,随机化方法增长了不肯定性,可是他们有利于减小状态空间数量,经过处理全部可能选择时使用类似的方法。咱们使用随机化去简化 Raft 中领导人选举算法。
Raft 是一种用来管理章节 2 中描述的复制日志的算法。图 2 为了参考之用,总结这个算法的简略版本,图 3 列举了这个算法的一些关键特性。图中的这些元素会在剩下的章节逐一介绍。
Raft 经过选举一个高贵的领导人,而后给予他所有的管理复制日志的责任来实现一致性。领导人从客户端接收日志条目,把日志条目复制到其余服务器上,而且当保证安全性的时候告诉其余的服务器应用日志条目到他们的状态机中。拥有一个领导人大大简化了对复制日志的管理。例如,领导人能够决定新的日志条目须要放在日志中的什么位置而不须要和其余服务器商议,而且数据都从领导人流向其余服务器。一个领导人能够宕机,能够和其余服务器失去链接,这时一个新的领导人会被选举出来。
经过领导人的方式,Raft 将一致性问题分解成了三个相对独立的子问题,这些问题会在接下来的子章节中进行讨论:
在展现一致性算法以后,这一章节会讨论可用性的一些问题和计时在系统的做用。
状态:
状态 | 全部服务器上持久存在的 |
---|---|
currentTerm | 服务器最后一次知道的任期号(初始化为 0,持续递增) |
votedFor | 在当前得到选票的候选人的 Id |
log[] | 日志条目集;每个条目包含一个用户状态机执行的指令,和收到时的任期号 |
状态 | 全部服务器上常常变的 |
---|---|
commitIndex | 已知的最大的已经被提交的日志条目的索引值 |
lastApplied | 最后被应用到状态机的日志条目索引值(初始化为 0,持续递增) |
状态 | 在领导人里常常改变的 (选举后从新初始化) |
---|---|
nextIndex[] | 对于每个服务器,须要发送给他的下一个日志条目的索引值(初始化为领导人最后索引值加一) |
matchIndex[] | 对于每个服务器,已经复制给他的日志的最高索引值 |
附加日志 RPC:
由领导人负责调用来复制日志指令;也会用做heartbeat
参数 | 解释 |
---|---|
term | 领导人的任期号 |
leaderId | 领导人的 Id,以便于跟随者重定向请求 |
prevLogIndex | 新的日志条目紧随以前的索引值 |
prevLogTerm | prevLogIndex 条目的任期号 |
entries[] | 准备存储的日志条目(表示心跳时为空;一次性发送多个是为了提升效率) |
leaderCommit | 领导人已经提交的日志的索引值 |
返回值 | 解释 |
---|---|
term | 当前的任期号,用于领导人去更新本身 |
success | 跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的日志时为真 |
接收者实现:
term < currentTerm
就返回 false (5.1 节)leaderCommit > commitIndex
,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个请求投票 RPC:
由候选人负责调用用来征集选票(5.2 节)
参数 | 解释 |
---|---|
term | 候选人的任期号 |
candidateId | 请求选票的候选人的 Id |
lastLogIndex | 候选人的最后日志条目的索引值 |
lastLogTerm | 候选人最后日志条目的任期号 |
返回值 | 解释 |
---|---|
term | 当前任期号,以便于候选人去更新本身的任期号 |
voteGranted | 候选人赢得了此张选票时为真 |
接收者实现:
term < currentTerm
返回 false (5.2 节)全部服务器需遵照的规则:
全部服务器:
commitIndex > lastApplied
,那么就 lastApplied 加一,并把log[lastApplied]
应用到状态机中(5.3 节)T > currentTerm
,那么就令 currentTerm 等于 T,并切换状态为跟随者(5.1 节)跟随者(5.2 节):
候选人(5.2 节):
在转变成候选人后就当即开始选举过程
领导人:
若是对于一个跟随者,最后日志条目的索引值大于等于 nextIndex,那么:发送从 nextIndex 开始的全部日志条目:
N > commitIndex
的 N,而且大多数的matchIndex[i] ≥ N
成立,而且log[N].term == currentTerm
成立,那么令 commitIndex 等于这个 N (5.3 和 5.4 节)图 2:一个关于 Raft 一致性算法的浓缩总结(不包括成员变换和日志压缩)。
特性 | 解释 |
---|---|
选举安全特性 | 对于一个给定的任期号,最多只会有一个领导人被选举出来(5.2 节) |
领导人只附加原则 | 领导人绝对不会删除或者覆盖本身的日志,只会增长(5.3 节) |
日志匹配原则 | 若是两个日志在相同的索引位置的日志条目的任期号相同,那么咱们就认为这个日志从头到这个索引位置之间所有彻底相同(5.3 节) |
领导人彻底特性 | 若是某个日志条目在某个任期号中已经被提交,那么这个条目必然出如今更大任期号的全部领导人中(5.4 节) |
状态机安全特性 | 若是一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其余任何的服务器在这个索引位置不会提交一个不一样的日志(5.4.3 节) |
图 3:Raft 在任什么时候候都保证以上的各个特性。
一个 Raft 集群包含若干个服务器节点;一般是 5 个,这容许整个系统容忍 2 个节点的失效。在任什么时候刻,每个服务器节点都处于这三个状态之一:领导人、跟随者或者候选人。在一般状况下,系统中只有一个领导人而且其余的节点所有都是跟随者。跟随者都是被动的:他们不会发送任何请求,只是简单的响应来自领导者或者候选人的请求。领导人处理全部的客户端请求(若是一个客户端和跟随者联系,那么跟随者会把请求重定向给领导人)。第三种状态,候选人,是用来在 5.2 节描述的选举新领导人时使用。图 4 展现了这些状态和他们之间的转换关系;这些转换关系会在接下来进行讨论。
图 4:服务器状态。跟随者只响应来自其余服务器的请求。若是跟随者接收不到消息,那么他就会变成候选人并发起一次选举。得到集群中大多数选票的候选人将成为领导者。在一个任期内,领导人一直都会是领导人直到本身宕机了。
图 5:时间被划分红一个个的任期,每一个任期开始都是一次选举。在选举成功后,领导人会管理整个集群直到任期结束。有时候选举会失败,那么这个任期就会没有领导人而结束。任期之间的切换能够在不一样的时间不一样的服务器上观察到。
Raft 把时间分割成任意长度的任期,如图 5。任期用连续的整数标记。每一段任期从一次选举开始,就像章节 5.2 描述的同样,一个或者多个候选人尝试成为领导者。若是一个候选人赢得选举,而后他就在接下来的任期内充当领导人的职责。在某些状况下,一次选举过程会形成选票的瓜分。在这种状况下,这一任期会以没有领导人结束;一个新的任期(和一次新的选举)会很快从新开始。Raft 保证了在一个给定的任期内,最多只有一个领导者。
不一样的服务器节点可能屡次观察到任期之间的转换,但在某些状况下,一个节点也可能观察不到任何一次选举或者整个任期全程。任期在 Raft 算法中充当逻辑时钟的做用,这会容许服务器节点查明一些过时的信息好比陈旧的领导者。每个节点存储一个当前任期号,这一编号在整个时期内单调的增加。当服务器之间通讯的时候会交换当前任期号;若是一个服务器的当前任期号比其余人小,那么他会更新本身的编号到较大的编号值。若是一个候选人或者领导者发现本身的任期号过时了,那么他会当即恢复成跟随者状态。若是一个节点接收到一个包含过时的任期号的请求,那么他会直接拒绝这个请求。
Raft 算法中服务器节点之间通讯使用远程过程调用(RPCs),而且基本的一致性算法只须要两种类型的 RPCs。请求投票(RequestVote) RPCs 由候选人在选举期间发起(章节 5.2),而后附加条目(AppendEntries)RPCs 由领导人发起,用来复制日志和提供一种心跳机制(章节 5.3)。第 7 节为了在服务器之间传输快照增长了第三种 RPC。当服务器没有及时的收到 RPC 的响应时,会进行重试, 而且他们可以并行的发起 RPCs 来得到最佳的性能。
Raft 使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是跟随者身份。一个服务器节点继续保持着跟随者状态只要他从领导人或者候选者处接收到有效的 RPCs。领导者周期性的向全部跟随者发送心跳包(即不包含日志项内容的附加日志项 RPCs)来维持本身的权威。若是一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,那么他就会认为系统中没有可用的领导者,而且发起选举以选出新的领导者。
要开始一次选举过程,跟随者先要增长本身的当前任期号而且转换到候选人状态。而后他会并行的向集群中的其余服务器节点发送请求投票的 RPCs 来给本身投票。候选人会继续保持着当前状态直到如下三件事情之一发生:(a) 他本身赢得了此次的选举,(b) 其余的服务器成为领导者,(c) 一段时间以后没有任何一个获胜的人。这些结果会分别的在下面的段落里进行讨论。
当一个候选人从整个集群的大多数服务器节点得到了针对同一个任期号的选票,那么他就赢得了此次选举并成为领导人。每个服务器最多会对一个任期号投出一张选票,按照先来先服务的原则(注意:5.4 节在投票上增长了一点额外的限制)。要求大多数选票的规则确保了最多只会有一个候选人赢得这次选举(图 3 中的选举安全性)。一旦候选人赢得选举,他就当即成为领导人。而后他会向其余的服务器发送心跳消息来创建本身的权威而且阻止新的领导人的产生。
在等待投票的时候,候选人可能会从其余的服务器接收到声明它是领导人的附加日志项 RPC。若是这个领导人的任期号(包含在这次的 RPC中)不小于候选人当前的任期号,那么候选人会认可领导人合法并回到跟随者状态。 若是这次 RPC 中的任期号比本身小,那么候选人就会拒绝此次的 RPC 而且继续保持候选人状态。
第三种可能的结果是候选人既没有赢得选举也没有输:若是有多个跟随者同时成为候选人,那么选票可能会被瓜分以致于没有候选人能够赢得大多数人的支持。当这种状况发生的时候,每个候选人都会超时,而后经过增长当前任期号来开始一轮新的选举。然而,没有其余机制的话,选票可能会被无限的重复瓜分。
Raft 算法使用随机选举超时时间的方法来确保不多会发生选票瓜分的状况,就算发生也能很快的解决。为了阻止选票起初就被瓜分,选举超时时间是从一个固定的区间(例如 150-300 毫秒)随机选择。这样能够把服务器都分散开以致于在大多数状况下只有一个服务器会选举超时;而后他赢得选举并在其余服务器超时以前发送心跳包。一样的机制被用在选票瓜分的状况下。每个候选人在开始一次选举的时候会重置一个随机的选举超时时间,而后在超时时间内等待投票的结果;这样减小了在新的选举中另外的选票瓜分的可能性。9.3 节展现了这种方案可以快速的选出一个领导人。
领导人选举这个例子,体现了可理解性原则是如何指导咱们进行方案设计的。起初咱们计划使用一种排名系统:每个候选人都被赋予一个惟一的排名,供候选人之间竞争时进行选择。若是一个候选人发现另外一个候选人拥有更高的排名,那么他就会回到跟随者状态,这样高排名的候选人可以更加容易的赢得下一次选举。可是咱们发现这种方法在可用性方面会有一点问题(若是高排名的服务器宕机了,那么低排名的服务器可能会超时并再次进入候选人状态。并且若是这个行为发生得足够快,则可能会致使整个选举过程都被重置掉)。咱们针对算法进行了屡次调整,可是每次调整以后都会有新的问题。最终咱们认为随机重试的方法是更加明显和易于理解的。
一旦一个领导人被选举出来,他就开始为客户端提供服务。客户端的每个请求都包含一条被复制状态机执行的指令。领导人把这条指令做为一条新的日志条目附加到日志中去,而后并行的发起附加条目 RPCs 给其余的服务器,让他们复制这条日志条目。当这条日志条目被安全的复制(下面会介绍),领导人会应用这条日志条目到它的状态机中而后把执行的结果返回给客户端。若是跟随者崩溃或者运行缓慢,再或者网络丢包,领导人会不断的重复尝试附加日志条目 RPCs (尽管已经回复了客户端)直到全部的跟随者都最终存储了全部的日志条目。
图 6:日志由有序序号标记的条目组成。每一个条目都包含建立时的任期号(图中框中的数字),和一个状态机须要执行的指令。一个条目当能够安全的被应用到状态机中去的时候,就认为是能够提交了。
日志以图 6 展现的方式组织。每个日志条目存储一条状态机指令和从领导人收到这条指令时的任期号。日志中的任期号用来检查是否出现不一致的状况,同时也用来保证图 3 中的某些性质。每一条日志条目同时也都有一个整数索引值来代表它在日志中的位置。
领导人来决定何时把日志条目应用到状态机中是安全的;这种日志条目被称为已提交。Raft 算法保证全部已提交的日志条目都是持久化的而且最终会被全部可用的状态机执行。在领导人将建立的日志条目复制到大多数的服务器上的时候,日志条目就会被提交(例如在图 6 中的条目 7)。同时,领导人的日志中以前的全部日志条目也都会被提交,包括由其余领导人建立的条目。5.4 节会讨论某些当在领导人改变以后应用这条规则的隐晦内容,同时他也展现了这种提交的定义是安全的。领导人跟踪了最大的将会被提交的日志项的索引,而且索引值会被包含在将来的全部附加日志 RPCs (包括心跳包),这样其余的服务器才能最终知道领导人的提交位置。一旦跟随者知道一条日志条目已经被提交,那么他也会将这个日志条目应用到本地的状态机中(按照日志的顺序)。
咱们设计了 Raft 的日志机制来维护一个不一样服务器的日志之间的高层次的一致性。这么作不只简化了系统的行为也使得更加可预计,同时他也是安全性保证的一个重要组件。Raft 维护着如下的特性,这些同时也组成了图 3 中的日志匹配特性:
第一个特性来自这样的一个事实,领导人最多在一个任期里在指定的一个日志索引位置建立一条日志条目,同时日志条目在日志中的位置也历来不会改变。第二个特性由附加日志 RPC 的一个简单的一致性检查所保证。在发送附加日志 RPC 的时候,领导人会把新的日志条目紧接着以前的条目的索引位置和任期号包含在里面。若是跟随者在它的日志中找不到包含相同索引位置和任期号的条目,那么他就会拒绝接收新的日志条目。一致性检查就像一个概括步骤:一开始空的日志状态确定是知足日志匹配特性的,而后一致性检查保护了日志匹配特性当日志扩展的时候。所以,每当附加日志 RPC 返回成功时,领导人就知道跟随者的日志必定是和本身相同的了。
在正常的操做中,领导人和跟随者的日志保持一致性,因此附加日志 RPC 的一致性检查历来不会失败。然而,领导人崩溃的状况会使得日志处于不一致的状态(老的领导人可能尚未彻底复制全部的日志条目)。这种不一致问题会在领导人和跟随者的一系列崩溃下加重。图 7 展现了跟随者的日志可能和新的领导人不一样的方式。跟随者可能会丢失一些在新的领导人中有的日志条目,他也可能拥有一些领导人没有的日志条目,或者二者都发生。丢失或者多出日志条目可能会持续多个任期。
图 7:当一个领导人成功当选时,跟随者多是任何状况(a-f)。每个盒子表示是一个日志条目;里面的数字表示任期号。跟随者可能会缺乏一些日志条目(a-b),可能会有一些未被提交的日志条目(c-d),或者两种状况都存在(e-f)。例如,场景 f 可能会这样发生,某服务器在任期 2 的时候是领导人,已附加了一些日志条目到本身的日志中,但在提交以前就崩溃了;很快这个机器就被重启了,在任期 3 从新被选为领导人,而且又增长了一些日志条目到本身的日志中;在任期 2 和任期 3 的日志被提交以前,这个服务器又宕机了,而且在接下来的几个任期里一直处于宕机状态。
在 Raft 算法中,领导人处理不一致是经过强制跟随者直接复制本身的日志来解决了。这意味着在跟随者中的冲突的日志条目会被领导人的日志覆盖。5.4 节会阐述如何经过增长一些限制来使得这样的操做是安全的。
要使得跟随者的日志进入和本身一致的状态,领导人必须找到最后二者达成一致的地方,而后删除从那个点以后的全部日志条目,发送本身的日志给跟随者。全部的这些操做都在进行附加日志 RPCs 的一致性检查时完成。领导人针对每个跟随者维护了一个 nextIndex,这表示下一个须要发送给跟随者的日志条目的索引地址。当一个领导人刚得到权力的时候,他初始化全部的 nextIndex 值为本身的最后一条日志的index加1(图 7 中的 11)。若是一个跟随者的日志和领导人不一致,那么在下一次的附加日志 RPC 时的一致性检查就会失败。在被跟随者拒绝以后,领导人就会减少 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得领导人和跟随者的日志达成一致。当这种状况发生,附加日志 RPC 就会成功,这时就会把跟随者冲突的日志条目所有删除而且加上领导人的日志。一旦附加日志 RPC 成功,那么跟随者的日志就会和领导人保持一致,而且在接下来的任期里一直继续保持。
若是须要的话,算法能够经过减小被拒绝的附加日志 RPCs 的次数来优化。例如,当附加日志 RPC 的请求被拒绝的时候,跟随者能够包含冲突的条目的任期号和本身存储的那个任期的最先的索引地址。借助这些信息,领导人能够减少 nextIndex 越过全部那个任期冲突的全部日志条目;这样就变成每一个任期须要一次附加条目 RPC 而不是每一个条目一次。在实践中,咱们十分怀疑这种优化是不是必要的,由于失败是不多发生的而且也不大可能会有这么多不一致的日志。
经过这种机制,领导人在得到权力的时候就不须要任何特殊的操做来恢复一致性。他只须要进行正常的操做,而后日志就能自动的在回复附加日志 RPC 的一致性检查失败的时候自动趋于一致。领导人历来不会覆盖或者删除本身的日志(图 3 的领导人只附加特性)。
日志复制机制展现出了第 2 节中形容的一致性特性:Raft 可以接受,复制并应用新的日志条目只要大部分的机器是工做的;在一般的状况下,新的日志条目能够在一次 RPC 中被复制给集群中的大多数机器;而且单个的缓慢的跟随者不会影响总体的性能。
前面的章节里描述了 Raft 算法是如何选举和复制日志的。然而,到目前为止描述的机制并不能充分的保证每个状态机会按照相同的顺序执行相同的指令。例如,一个跟随者可能会进入不可用状态同时领导人已经提交了若干的日志条目,而后这个跟随者可能会被选举为领导人而且覆盖这些日志条目;所以,不一样的状态机可能会执行不一样的指令序列。
这一节经过在领导选举的时候增长一些限制来完善 Raft 算法。这一限制保证了任何的领导人对于给定的任期号,都拥有了以前任期的全部被提交的日志条目(图 3 中的领导人完整特性)。增长这一选举时的限制,咱们对于提交时的规则也更加清晰。最终,咱们将展现对于领导人完整特性的简要证实,而且说明领导人是如何领导复制状态机的作出正确行为的。
在任何基于领导人的一致性算法中,领导人都必须存储全部已经提交的日志条目。在某些一致性算法中,例如 Viewstamped Replication,某个节点即便是一开始并无包含全部已经提交的日志条目,它也能被选为领导者。这些算法都包含一些额外的机制来识别丢失的日志条目并把他们传送给新的领导人,要么是在选举阶段要么在以后很快进行。不幸的是,这种方法会致使至关大的额外的机制和复杂性。Raft 使用了一种更加简单的方法,它能够保证全部以前的任期号中已经提交的日志条目在选举的时候都会出如今新的领导人中,不须要传送这些日志条目给领导人。这意味着日志条目的传送是单向的,只从领导人传给跟随者,而且领导人从不会覆盖自身本地日志中已经存在的条目。
Raft 使用投票的方式来阻止一个候选人赢得选举除非这个候选人包含了全部已经提交的日志条目。候选人为了赢得选举必须联系集群中的大部分节点,这意味着每个已经提交的日志条目在这些服务器节点中确定存在于至少一个节点上。若是候选人的日志至少和大多数的服务器节点同样新(这个新的定义会在下面讨论),那么他必定持有了全部已经提交的日志条目。请求投票 RPC 实现了这样的限制: RPC 中包含了候选人的日志信息,而后投票人会拒绝掉那些日志没有本身新的投票请求。
Raft 经过比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新。若是两份日志最后的条目的任期号不一样,那么任期号大的日志更加新。若是两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。
如同 5.3 节介绍的那样,领导人知道一条当前任期内的日志记录是能够被提交的,只要它被存储到了大多数的服务器上。若是一个领导人在提交日志条目以前崩溃了,将来后续的领导人会继续尝试复制这条日志记录。然而,一个领导人不能判定一个以前任期里的日志条目被保存到大多数服务器上的时候就必定已经提交了。图 8 展现了一种状况,一条已经被存储到大多数节点上的老日志条目,也依然有可能会被将来的领导人覆盖掉。
图 8:如图的时间序列展现了为何领导人没法决定对老任期号的日志条目进行提交。在 (a) 中,S1 是领导者,部分的复制了索引位置 2 的日志条目。在 (b) 中,S1 崩溃了,而后 S5 在任期 3 里经过 S三、S4 和本身的选票赢得选举,而后从客户端接收了一条不同的日志条目放在了索引 2 处。而后到 (c),S5 又崩溃了;S1 从新启动,选举成功,开始复制日志。在这时,来自任期 2 的那条日志已经被复制到了集群中的大多数机器上,可是尚未被提交。若是 S1 在 (d) 中又崩溃了,S5 能够从新被选举成功(经过来自 S2,S3 和 S4 的选票),而后覆盖了他们在索引 2 处的日志。反之,若是在崩溃以前,S1 把本身主导的新任期里产生的日志条目复制到了大多数机器上,就如 (e) 中那样,那么在后面任期里面这些新的日志条目就会被提交(由于S5 就不可能选举成功)。 这样在同一时刻就同时保证了,以前的全部老的日志条目就会被提交。
为了消除图 8 里描述的状况,Raft 永远不会经过计算副本数目的方式去提交一个以前任期内的日志条目。只有领导人当前任期里的日志条目经过计算副本数目能够被提交;一旦当前任期的日志条目以这种方式被提交,那么因为日志匹配特性,以前的日志条目也都会被间接的提交。在某些状况下,领导人能够安全的知道一个老的日志条目是否已经被提交(例如,该条目是否存储到全部服务器上),可是 Raft 为了简化问题使用一种更加保守的方法。
当领导人复制以前任期里的日志时,Raft 会为全部日志保留原始的任期号, 这在提交规则上产生了额外的复杂性。在其余的一致性算法中,若是一个新的领导人要从新复制以前的任期里的日志时,它必须使用当前新的任期号。Raft 使用的方法更加容易辨别出日志,由于它能够随着时间和日志的变化对日志维护着同一个任期编号。另外,和其余的算法相比,Raft 中的新领导人只须要发送更少日志条目(其余算法中必须在他们被提交以前发送更多的冗余日志条目来为他们从新编号)。
在给定了完整的 Raft 算法以后,咱们如今能够更加精确的讨论领导人完整性特性(这一讨论基于 9.2 节的安全性证实)。咱们假设领导人彻底性特性是不存在的,而后咱们推出矛盾来。假设任期 T 的领导人(领导人 T)在任期内提交了一条日志条目,可是这条日志条目没有被存储到将来某个任期的领导人的日志中。设大于 T 的最小任期 U 的领导人 U 没有这条日志条目。
图 9:若是 S1 (任期 T 的领导者)提交了一条新的日志在它的任期里,而后 S5 在以后的任期 U 里被选举为领导人,而后至少会有一个机器,如 S3,既拥有来自 S1 的日志,也给 S5 投票了。
经过领导人彻底特性,咱们就能证实图 3 中的状态机安全特性,即若是服务器已经在某个给定的索引值应用了日志条目到本身的状态机里,那么其余的服务器不会应用一个不同的日志到同一个索引值上。在一个服务器应用一条日志条目到他本身的状态机中时,他的日志必须和领导人的日志,在该条目和以前的条目上相同,而且已经被提交。如今咱们来考虑在任何一个服务器应用一个指定索引位置的日志的最小任期;日志彻底特性保证拥有更高任期号的领导人会存储相同的日志条目,因此以后的任期里应用某个索引位置的日志条目也会是相同的值。所以,状态机安全特性是成立的。
最后,Raft 要求服务器按照日志中索引位置顺序应用日志条目。和状态机安全特性结合起来看,这就意味着全部的服务器会应用相同的日志序列集到本身的状态机中,而且是按照相同的顺序。
到目前为止,咱们都只关注了领导人崩溃的状况。跟随者和候选人崩溃后的处理方式比领导人要简单的多,而且他们的处理方式是相同的。若是跟随者或者候选人崩溃了,那么后续发送给他们的 RPCs 都会失败。Raft 中处理这种失败就是简单的经过无限的重试;若是崩溃的机器重启了,那么这些 RPC 就会完整的成功。若是一个服务器在完成了一个 RPC,可是尚未响应的时候崩溃了,那么在他从新启动以后就会再次收到一样的请求。Raft 的 RPCs 都是幂等的,因此这样重试不会形成任何问题。例如一个跟随者若是收到附加日志请求可是他已经包含了这一日志,那么他就会直接忽略这个新的请求。
Raft 的要求之一就是安全性不能依赖时间:整个系统不能由于某些事件运行的比预期快一点或者慢一点就产生了错误的结果。可是,可用性(系统能够及时的响应客户端)不可避免的要依赖于时间。例如,若是消息交换比服务器故障间隔时间长,候选人将没有足够长的时间来赢得选举;没有一个稳定的领导人,Raft 将没法工做。
领导人选举是 Raft 中对时间要求最为关键的方面。Raft 能够选举并维持一个稳定的领导人,只要系统知足下面的时间要求:
广播时间(broadcastTime) << 选举超时时间(electionTimeout) << 平均故障间隔时间(MTBF)
在这个不等式中,广播时间指的是从一个服务器并行的发送 RPCs 给集群中的其余服务器并接收响应的平均时间;选举超时时间就是在 5.2 节中介绍的选举的超时时间限制;而后平均故障间隔时间就是对于一台服务器而言,两次故障之间的平均时间。广播时间必须比选举超时时间小一个量级,这样领导人才可以发送稳定的心跳消息来阻止跟随者开始进入选举状态;经过随机化选举超时时间的方法,这个不等式也使得选票瓜分的状况变得不可能。选举超时时间应该要比平均故障间隔时间小上几个数量级,这样整个系统才能稳定的运行。当领导人崩溃后,整个系统会大约至关于选举超时的时间里不可用;咱们但愿这种状况在整个系统的运行中不多出现。
广播时间和平均故障间隔时间是由系统决定的,可是选举超时时间是咱们本身选择的。Raft 的 RPCs 须要接收方将信息持久化的保存到稳定存储中去,因此广播时间大约是 0.5 毫秒到 20 毫秒,取决于存储的技术。所以,选举超时时间可能须要在 10 毫秒到 500 毫秒之间。大多数的服务器的平均故障间隔时间都在几个月甚至更长,很容易知足时间的需求。
到目前为止,咱们都假设集群的配置(加入到一致性算法的服务器集合)是固定不变的。可是在实践中,偶尔是会改变集群的配置的,例如替换那些宕机的机器或者改变复制级别。尽管能够经过暂停整个集群,更新全部配置,而后重启整个集群的方式来实现,可是在更改的时候集群会不可用。另外,若是存在手工操做步骤,那么就会有操做失误的风险。为了不这样的问题,咱们决定自动化配置改变而且将其归入到 Raft 一致性算法中来。
为了让配置修改机制可以安全,那么在转换的过程当中不可以存在任什么时候间点使得两个领导人同时被选举成功在同一个任期里。不幸的是,任何服务器直接从旧的配置直接转换到新的配置的方案都是不安全的。一次性自动的转换全部服务器是不可能的,因此在转换期间整个集群存在划分红两个独立的大多数群体的可能性(见图 10)。
图 10:直接从一种配置转到新的配置是十分不安全的,由于各个机器可能在任何的时候进行转换。在这个例子中,集群配额从 3 台机器变成了 5 台。不幸的是,存在这样的一个时间点,两个不一样的领导人在同一个任期里均可以被选举成功。一个是经过旧的配置,一个经过新的配置。
为了保证安全性,配置更改必须使用两阶段方法。目前有不少种两阶段的实现。例如,有些系统在第一阶段停掉旧的配置因此集群就不能处理客户端请求;而后在第二阶段在启用新的配置。在 Raft 中,集群先切换到一个过渡的配置,咱们称之为共同一致;一旦共同一致已经被提交了,那么系统就切换到新的配置上。共同一致是老配置和新配置的结合:
共同一致容许独立的服务器在不影响安全性的前提下,在不一样的时间进行配置转换过程。此外,共同一致可让集群在配置转换的过程人依然响应客户端的请求。
集群配置在复制日志中以特殊的日志条目来存储和通讯;图 11 展现了配置转换的过程。当一个领导人接收到一个改变配置从 C-old 到 C-new 的请求,他会为了共同一致存储配置(图中的 C-old,new),之前面描述的日志条目和副本的形式。一旦一个服务器将新的配置日志条目增长到它的日志中,他就会用这个配置来作出将来全部的决定(服务器老是使用最新的配置,不管他是否已经被提交)。这意味着领导人要使用 C-old,new 的规则来决定日志条目 C-old,new 何时须要被提交。若是领导人崩溃了,被选出来的新领导人多是使用 C-old 配置也多是 C-old,new 配置,这取决于赢得选举的候选人是否已经接收到了 C-old,new 配置。在任何状况下, C-new 配置在这一时期都不会单方面的作出决定。
一旦 C-old,new 被提交,那么不管是 C-old 仍是 C-new,在没有通过他人批准的状况下都不可能作出决定,而且领导人彻底特性保证了只有拥有 C-old,new 日志条目的服务器才有可能被选举为领导人。这个时候,领导人建立一条关于 C-new 配置的日志条目并复制给集群就是安全的了。再者,每一个服务器在见到新的配置的时候就会当即生效。当新的配置在 C-new 的规则下被提交,旧的配置就变得可有可无,同时不使用新的配置的服务器就能够被关闭了。如图 11,C-old 和 C-new 没有任何机会同时作出单方面的决定;这保证了安全性。
图 11:一个配置切换的时间线。虚线表示已经被建立可是尚未被提交的条目,实线表示最后被提交的日志条目。领导人首先建立了 C-old,new 的配置条目在本身的日志中,并提交到 C-old,new 中(C-old 的大多数和 C-new 的大多数)。而后他建立 C-new 条目并提交到 C-new 中的大多数。这样就不存在 C-new 和 C-old 能够同时作出决定的时间点。
在关于从新配置还有三个问题须要提出。第一个问题是,新的服务器可能初始化没有存储任何的日志条目。当这些服务器以这种状态加入到集群中,那么他们须要一段时间来更新追赶,这时还不能提交新的日志条目。为了不这种可用性的间隔时间,Raft 在配置更新的时候使用了一种额外的阶段,在这个阶段,新的服务器以没有投票权身份加入到集群中来(领导人复制日志给他们,可是不考虑他们是大多数)。一旦新的服务器追遇上了集群中的其余机器,从新配置能够像上面描述的同样处理。
第二个问题是,集群的领导人可能不是新配置的一员。在这种状况下,领导人就会在提交了 C-new 日志以后退位(回到跟随者状态)。这意味着有这样的一段时间,领导人管理着集群,可是不包括他本身;他复制日志可是不把他本身算做是大多数之一。当 C-new 被提交时,会发生领导人过渡,由于这时是最先新的配置能够独立工做的时间点(将老是可以在 C-new 配置下选出新的领导人)。在此以前,可能只能从 C-old 中选出领导人。
第三个问题是,移除不在 C-new 中的服务器可能会扰乱集群。这些服务器将不会再接收到心跳,因此当选举超时,他们就会进行新的选举过程。他们会发送拥有新的任期号的请求投票 RPCs,这样会致使当前的领导人回退成跟随者状态。新的领导人最终会被选出来,可是被移除的服务器将会再次超时,而后这个过程会再次重复,致使总体可用性大幅下降。
为了不这个问题,当服务器确认当前领导人存在时,服务器会忽略请求投票 RPCs。特别的,当服务器在当前最小选举超时时间内收到一个请求投票 RPC,他不会更新当前的任期号或者投出选票。这不会影响正常的选举,每一个服务器在开始一次选举以前,至少等待一个最小选举超时时间。然而,这有利于避免被移除的服务器扰乱:若是领导人可以发送心跳给集群,那么他就不会被更大的任期号废黜。
Raft 的日志在正常操做中不断的增加,可是在实际的系统中,日志不能无限制的增加。随着日志不断增加,他会占用愈来愈多的空间,花费愈来愈多的时间来重置。若是没有必定的机制去清除日志里积累的陈旧的信息,那么会带来可用性问题。
快照是最简单的压缩方法。在快照系统中,整个系统的状态都以快照的形式写入到稳定的持久化存储中,而后到那个时间点以前的日志所有丢弃。快照技术被使用在 Chubby 和 ZooKeeper 中,接下来的章节会介绍 Raft 中的快照技术。
增量压缩的方法,例如日志清理或者日志结构合并树,都是可行的。这些方法每次只对一小部分数据进行操做,这样就分散了压缩的负载压力。首先,他们先选择一个已经积累的大量已经被删除或者被覆盖对象的区域,而后重写那个区域还活跃的对象,以后释放那个区域。和简单操做整个数据集合的快照相比,须要增长复杂的机制来实现。状态机能够实现 LSM tree 使用和快照相同的接口,可是日志清除方法就须要修改 Raft 了。
图 12:一个服务器用新的快照替换了从 1 到 5 的条目,快照值存储了当前的状态。快照中包含了最后的索引位置和任期号。
图 12 展现了 Raft 中快照的基础思想。每一个服务器独立的建立快照,只包括已经被提交的日志。主要的工做包括将状态机的状态写入到快照中。Raft 也包含一些少许的元数据到快照中:最后被包含索引指的是被快照取代的最后的条目在日志中的索引值(状态机最后应用的日志),最后被包含的任期指的是该条目的任期号。保留这些数据是为了支持快照后紧接着的第一个条目的附加日志请求时的一致性检查,由于这个条目须要前一日志条目的索引值和任期号。为了支持集群成员更新(第 6 节),快照中也将最后的一次配置做为最后一个条目存下来。一旦服务器完成一次快照,他就能够删除最后索引位置以前的全部日志和快照了。
尽管一般服务器都是独立的建立快照,可是领导人必须偶尔的发送快照给一些落后的跟随者。这一般发生在当领导人已经丢弃了下一条须要发送给跟随者的日志条目的时候。幸运的是这种状况不是常规操做:一个与领导人保持同步的跟随者一般都会有这个条目。然而一个运行很是缓慢的跟随者或者新加入集群的服务器(第 6 节)将不会有这个条目。这时让这个跟随者更新到最新的状态的方式就是经过网络把快照发送给他们。
安装快照 RPC:
由领导人调用以将快照的分块发送给跟随者。领导者老是按顺序发送分块。
参数 | 解释 |
---|---|
term | 领导人的任期号 |
leaderId | 领导人的 Id,以便于跟随者重定向请求 |
lastIncludedIndex | 快照中包含的最后日志条目的索引值 |
lastIncludedTerm | 快照中包含的最后日志条目的任期号 |
offset | 分块在快照中的字节偏移量 |
data[] | 原始数据 |
done | 若是这是最后一个分块则为 true |
结果 | 解释 |
---|---|
term | 当前任期号(currentTerm),便于领导人更新本身 |
接收者实现:
term < currentTerm
就当即回复图 13:一个关于安装快照的简要概述。为了便于传输,快照都是被分红分块的;每一个分块都给了跟随者生命的迹象,因此跟随者能够重置选举超时计时器。
在这种状况下领导人使用一种叫作安装快照的新的 RPC 来发送快照给太落后的跟随者;见图 13。当跟随者经过这种 RPC 接收到快照时,他必须本身决定对于已经存在的日志该如何处理。一般快照会包含没有在接收者日志中存在的信息。在这种状况下,跟随者丢弃其整个日志;它所有被快照取代,而且可能包含与快照冲突的未提交条目。若是接收到的快照是本身日志的前面部分(因为网络重传或者错误),那么被快照包含的条目将会被所有删除,可是快照后面的条目仍然有效,必须保留。
这种快照的方式背离了 Raft 的强领导人原则,由于跟随者能够在不知道领导人状况下建立快照。可是咱们认为这种背离是值得的。领导人的存在,是为了解决在达成一致性的时候的冲突,可是在建立快照的时候,一致性已经达成,这时不存在冲突了,因此没有领导人也是能够的。数据依然是从领导人传给跟随者,只是跟随者能够从新组织他们的数据了。
咱们考虑过一种替代的基于领导人的快照方案,即只有领导人建立快照,而后发送给全部的跟随者。可是这样作有两个缺点。第一,发送快照会浪费网络带宽而且延缓了快照处理的时间。每一个跟随者都已经拥有了全部产生快照须要的信息,并且很显然,本身从本地的状态中建立快照比经过网络接收别人发来的要经济。第二,领导人的实现会更加复杂。例如,领导人须要发送快照的同时并行的将新的日志条目发送给跟随者,这样才不会阻塞新的客户端请求。
还有两个问题影响了快照的性能。首先,服务器必须决定何时应该建立快照。若是快照建立的过于频繁,那么就会浪费大量的磁盘带宽和其余资源;若是建立快照频率过低,他就要承受耗尽存储容量的风险,同时也增长了从日志重建的时间。一个简单的策略就是当日志大小达到一个固定大小的时候就建立一次快照。若是这个阈值设置的显著大于指望的快照的大小,那么快照对磁盘压力的影响就会很小了。
第二个影响性能的问题就是写入快照须要花费显著的一段时间,而且咱们还不但愿影响到正常操做。解决方案是经过写时复制的技术,这样新的更新就能够被接收而不影响到快照。例如,具备函数式数据结构的状态机自然支持这样的功能。另外,操做系统的写时复制技术的支持(如 Linux 上的 fork)能够被用来建立完整的状态机的内存快照(咱们的实现就是这样的)。
这一节将介绍客户端是如何和 Raft 进行交互的,包括客户端如何发现领导人和 Raft 是如何支持线性化语义的。这些问题对于全部基于一致性的系统都存在,而且 Raft 的解决方案和其余的也差很少。
Raft 中的客户端发送全部请求给领导人。当客户端启动的时候,他会随机挑选一个服务器进行通讯。若是客户端第一次挑选的服务器不是领导人,那么那个服务器会拒绝客户端的请求而且提供他最近接收到的领导人的信息(附加条目请求包含了领导人的网络地址)。若是领导人已经崩溃了,那么客户端的请求就会超时;客户端以后会再次重试随机挑选服务器的过程。
咱们 Raft 的目标是要实现线性化语义(每一次操做当即执行,只执行一次,在他调用和收到回复之间)。可是,如上述,Raft 是能够执行同一条命令屡次的:例如,若是领导人在提交了这条日志以后,可是在响应客户端以前崩溃了,那么客户端会和新的领导人重试这条指令,致使这条命令就被再次执行了。解决方案就是客户端对于每一条指令都赋予一个惟一的序列号。而后,状态机跟踪每条指令最新的序列号和相应的响应。若是接收到一条指令,它的序列号已经被执行了,那么就当即返回结果,而不从新执行指令。
只读的操做能够直接处理而不须要记录日志。可是,在不增长任何限制的状况下,这么作可能会冒着返回脏数据的风险,由于领导人响应客户端请求时可能已经被新的领导人做废了,可是他还不知道。线性化的读操做必须不能返回脏数据,Raft 须要使用两个额外的措施在不使用日志的状况下保证这一点。首先,领导人必须有关于被提交日志的最新信息。领导人彻底特性保证了领导人必定拥有全部已经被提交的日志条目,可是在他任期开始的时候,他可能不知道那些是已经被提交的。为了知道这些信息,他须要在他的任期里提交一条日志条目。Raft 中经过领导人在任期开始的时候提交一个空白的没有任何操做的日志条目到日志中去来实现。第二,领导人在处理只读的请求以前必须检查本身是否已经被废黜了(他本身的信息已经变脏了若是一个更新的领导人被选举出来)。Raft 中经过让领导人在响应只读请求以前,先和集群中的大多数节点交换一次心跳信息来处理这个问题。可选的,领导人能够依赖心跳机制来实现一种租约的机制,可是这种方法依赖时间来保证安全性(假设时间偏差是有界的)。
咱们已经为 RAMCloud 实现了 Raft 算法做为存储配置信息的复制状态机的一部分,而且帮助 RAMCloud 协调故障转移。这个 Raft 实现包含大约 2000 行 C++ 代码,其中不包括测试、注释和空行。这些代码是开源的。同时也有大约 25 个其余独立的第三方的基于这篇论文草稿的开源实现,针对不一样的开发场景。同时,不少公司已经部署了基于 Raft 的系统。
这一节会从三个方面来评估 Raft 算法:可理解性、正确性和性能。
为了和 Paxos 比较 Raft 算法的可理解能力,咱们针对高层次的本科生和研究生,在斯坦福大学的高级操做系统课程和加州大学伯克利分校的分布式计算课程上,进行了一次学习的实验。咱们分别拍了针对 Raft 和 Paxos 的视频课程,并准备了相应的小测验。Raft 的视频讲课覆盖了这篇论文的全部内容除了日志压缩;Paxos 讲课包含了足够的资料来建立一个等价的复制状态机,包括单决策 Paxos,多决策 Paxos,从新配置和一些实际系统须要的性能优化(例如领导人选举)。小测验测试一些对算法的基本理解和解释一些边角的示例。每一个学生都是看完第一个视频,回答相应的测试,再看第二个视频,回答相应的测试。大约有一半的学生先进行 Paxos 部分,而后另外一半先进行 Raft 部分,这是为了说明二者从第一部分的算法学习中得到的表现和经验的差别。咱们计算参加人员的每个小测验的得分来看参与者是否在 Raft 算法上更加容易理解。
咱们尽量的使得 Paxos 和 Raft 的比较更加公平。这个实验偏心 Paxos 表如今两个方面:43 个参加者中有 15 我的在以前有一些 Paxos 的经验,而且 Paxos 的视频要长 14%。如表格 1 总结的那样,咱们采起了一些措施来减轻这种潜在的偏见。咱们全部的材料均可供审查。
关心 | 缓和偏见采起的手段 | 可供查看的材料 |
---|---|---|
相同的讲课质量 | 二者使用同一个讲师。Paxos 使用的是如今不少大学里常用的。Paxos 会长 14%。 | 视频 |
相同的测验难度 | 问题以难度分组,在两个测验里成对出现。 | 小测验 |
公平评分 | 使用评价量规。随机顺序打分,两个测验交替进行。 | 评价量规(rubric) |
表 1:考虑到可能会存在的偏见,对于每种状况的解决方法,和相应的材料。
参加者平均在 Raft 的测验中比 Paxos 高 4.9 分(总分 60,那么 Raft 的平均得分是 25.7,而 Paxos 是 20.8);图 14 展现了每一个参与者的得分。配置t-检验(又称student‘s t-test)代表,在 95% 的可信度下,真实的 Raft 分数分布至少比 Paxos 高 2.5 分。
图 14:一个散点图表示了 43 个学生在 Paxos 和 Raft 的小测验中的成绩。在对角线之上的点表示在 Raft 得到了更高分数的学生。
咱们也创建了一个线性回归模型来预测一个新的学生的测验成绩,基于如下三个因素:他们使用的是哪一个小测验,以前对 Paxos 的经验,和学习算法的顺序。模型预测,对小测验的选择会产生 12.5 分的差异。这显著的高于以前的 4.9 分,由于不少学生在以前都已经有了对于 Paxos 的经验,这至关明显的帮助 Paxos,对 Raft 就没什么太大影响了。可是奇怪的是,模型预测对于先进行 Paxos 小测验的人而言,Raft的得分低了6.3分; 虽然咱们不知道为何,这彷佛在统计上是有意义的。
咱们同时也在测验以后调查了参与者,他们认为哪一个算法更加容易实现和解释;这个的结果在图 15 上。压倒性的结果代表 Raft 算法更加容易实现和解释(41 人中的 33个)。可是,这种本身报告的结果不如参与者的成绩更加可信,而且参与者可能由于咱们的 Raft 更加易于理解的假说而产生偏见。
图 15:经过一个 5 分制的问题,参与者(左边)被问哪一个算法他们以为在一个高效正确的系统里更容易实现,右边被问哪一个更容易向学生解释。
关于 Raft 用户学习有一个更加详细的讨论。
在第 5 节,咱们已经制定了正式的规范,和对一致性机制的安全性证实。这个正式规范使用 TLA+ 规范语言使图 2 中总结的信息很是清晰。它长约400行,并做为证实的主题。同时对于任何想实现 Raft 的人也是十分有用的。咱们经过 TLA 证实系统很是机械的证实了日志彻底特性。然而,这个证实依赖的约束前提尚未被机械证实(例如,咱们尚未证实规范的类型安全)。并且,咱们已经写了一个非正式的证实关于状态机安全性是完备的,而且是至关清晰的(大约 3500 个词)。
Raft 和其余一致性算法例如 Paxos 有着差很少的性能。在性能方面,最重要的关注点是,当领导人被选举成功时,何时复制新的日志条目。Raft 经过不多数量的消息包(一轮从领导人到集群大多数机器的消息)就达成了这个目的。同时,进一步提高 Raft 的性能也是可行的。例如,很容易经过支持批量操做和管道操做来提升吞吐量和下降延迟。对于其余一致性算法已经提出过不少性能优化方案;其中有不少也能够应用到 Raft 中来,可是咱们暂时把这个问题放到将来的工做中去。
咱们使用咱们本身的 Raft 实现来衡量 Raft 领导人选举的性能而且回答两个问题。首先,领导人选举的过程收敛是否快速?第二,在领导人宕机以后,最小的系统宕机时间是多久?
图 16:发现并替换一个已经崩溃的领导人的时间。上面的图考察了在选举超时时间上的随机化程度,下面的图考察了最小选举超时时间。每条线表明了 1000 次实验(除了 150-150 毫秒只试了 100 次),和相应的肯定的选举超时时间。例如,150-155 毫秒意思是,选举超时时间从这个区间范围内随机选择并肯定下来。这个实验在一个拥有 5 个节点的集群上进行,其广播时延大约是 15 毫秒。对于 9 个节点的集群,结果也差很少。
为了衡量领导人选举,咱们反复的使一个拥有五个节点的服务器集群的领导人宕机,并计算须要多久才能发现领导人已经宕机并选出一个新的领导人(见图 16)。为了构建一个最坏的场景,在每一的尝试里,服务器都有不一样长度的日志,意味着有些候选人是没有成为领导人的资格的。另外,为了促成选票瓜分的状况,咱们的测试脚本在终止领导人以前同步的发送了一次心跳广播(这大约和领导人在崩溃前复制一个新的日志给其余机器很像)。领导人均匀的随机的在心跳间隔里宕机,也就是最小选举超时时间的一半。所以,最小宕机时间大约就是最小选举超时时间的一半。
图 16 中上面的图代表,只须要在选举超时时间上使用不多的随机化就能够大大避免选票被瓜分的状况。在没有随机化的状况下,在咱们的测试里,选举过程每每都须要花费超过 10 秒钟因为太多的选票瓜分的状况。仅仅增长 5 毫秒的随机化时间,就大大的改善了选举过程,如今平均的宕机时间只有 287 毫秒。增长更多的随机化时间能够大大改善最坏状况:经过增长 50 毫秒的随机化时间,最坏的完成状况(1000 次尝试)只要 513 毫秒。
图 16 中下面的图显示,经过减小选举超时时间能够减小系统的宕机时间。在选举超时时间为 12-24 毫秒的状况下,只须要平均 35 毫秒就能够选举出新的领导人(最长的一次花费了 152 毫秒)。然而,进一步下降选举超时时间的话就会违反 Raft 的时间不等式需求:在选举新领导人以前,领导人就很难发送完心跳包。这会致使没有意义的领导人改变并下降了系统总体的可用性。咱们建议使用更为保守的选举超时时间,好比 150-300 毫秒;这样的时间不大可能致使没有意义的领导人改变,并且依然提供不错的可用性。
已经有不少关于一致性算法的工做被发表出来,其中不少均可以归到下面的类别中:
Raft 和 Paxos 最大的不一样之处就在于 Raft 的强领导特性:Raft 使用领导人选举做为一致性协议里必不可少的部分,而且将尽量多的功能集中到了领导人身上。这样就能够使得算法更加容易理解。例如,在 Paxos 中,领导人选举和基本的一致性协议是正交的:领导人选举仅仅是性能优化的手段,并且不是一致性所必需要求的。可是,这样就增长了多余的机制:Paxos 同时包含了针对基本一致性要求的两阶段提交协议和针对领导人选举的独立的机制。相比较而言,Raft 就直接将领导人选举归入到一致性算法中,并做为两阶段一致性的第一步。这样就减小了不少机制。
像 Raft 同样,VR 和 ZooKeeper 也是基于领导人的,所以他们也拥有一些 Raft 的优势。可是,Raft 比 VR 和 ZooKeeper 拥有更少的机制由于 Raft 尽量的减小了非领导人的功能。例如,Raft 中日志条目都遵循着从领导人发送给其余人这一个方向:附加条目 RPC 是向外发送的。在 VR 中,日志条目的流动是双向的(领导人能够在选举过程当中接收日志);这就致使了额外的机制和复杂性。根据 ZooKeeper 公开的资料看,它的日志条目也是双向传输的,可是它的实现更像 Raft。
和上述咱们说起的其余基于一致性的日志复制算法中,Raft 的消息类型更少。例如,咱们数了一下 VR 和 ZooKeeper 使用的用来基本一致性须要和成员改变的消息数(排除了日志压缩和客户端交互,由于这些都比较独立且和算法关系不大)。VR 和 ZooKeeper 都分别定义了 10 中不一样的消息类型,相对的,Raft 只有 4 中消息类型(两种 RPC 请求和对应的响应)。Raft 的消息都稍微比其余算法的要信息量大,可是都很简单。另外,VR 和 ZooKeeper 都在领导人改变时传输了整个日志;因此为了可以实践中使用,额外的消息类型就很必要了。
Raft 的强领导人模型简化了整个算法,可是同时也排斥了一些性能优化的方法。例如,平等主义 Paxos (EPaxos)在某些没有领导人的状况下能够达到很高的性能。平等主义 Paxos 充分发挥了在状态机指令中的交换性。任何服务器均可以在一轮通讯下就提交指令,除非其余指令同时被提出了。然而,若是指令都是并发的被提出,而且互相之间不通讯沟通,那么 EPaxos 就须要额外的一轮通讯。由于任何服务器均可以提交指令,因此 EPaxos 在服务器之间的负载均衡作的很好,而且很容易在 WAN 网络环境下得到很低的延迟。可是,他在 Paxos 上增长了很是明显的复杂性。
一些集群成员变换的方法已经被提出或者在其余的工做中被实现,包括 Lamport 的原始的讨论,VR 和 SMART。咱们选择使用共同一致的方法由于他对一致性协议的其余部分影响很小,这样咱们只须要不多的一些机制就能够实现成员变换。Lamport 的基于 α 的方法之因此没有被 Raft 选择是由于它假设在没有领导人的状况下也能够达到一致性。和 VR 和 SMART 相比较,Raft 的从新配置算法能够在不限制正常请求处理的状况下进行;相比较的,VR 须要中止全部的处理过程,SMART 引入了一个和 α 相似的方法,限制了请求处理的数量。Raft 的方法同时也须要更少的额外机制来实现,和 VR、SMART 比较而言。
算法的设计一般会把正确性,效率或者简洁做为主要的目标。尽管这些都是颇有意义的目标,可是咱们相信,可理解性也是同样的重要。在开发者把算法应用到实际的系统中以前,这些目标没有一个会被实现,这些都会必然的偏离发表时的形式。除非开发人员对这个算法有着很深的理解而且有着直观的感受,不然将会对他们而言很难在实现的时候保持原有指望的特性。
在这篇论文中,咱们尝试解决分布式一致性问题,可是一个广为接受可是十分使人费解的算法 Paxos 已经困扰了无数学生和开发者不少年了。咱们创造了一种新的算法 Raft,显而易见的比 Paxos 要容易理解。咱们同时也相信,Raft 也能够为实际的实现提供坚实的基础。把可理解性做为设计的目标改变了咱们设计 Raft 的方式;随着设计的进展,咱们发现本身重复使用了一些技术,好比分解问题和简化状态空间。这些技术不只提高了 Raft 的可理解性,同时也使咱们坚信其正确性。
这项研究必须感谢如下人员的支持:Ali Ghodsi,David Mazie`res,和伯克利 CS 294-91 课程、斯坦福 CS 240 课程的学生。Scott Klemmer 帮咱们设计了用户调查,Nelson Ray 建议咱们进行统计学的分析。在用户调查时使用的关于 Paxos 的幻灯片很大一部分是从 Lorenzo Alvisi 的幻灯片上借鉴过来的。特别的,很是感谢 DavidMazieres 和 Ezra Hoch,他们找到了 Raft 中一些难以发现的漏洞。许多人提供了关于这篇论文十分有用的反馈和用户调查材料,包括 Ed Bugnion,Michael Chan,Hugues Evrard,Daniel Giffin,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,Nicolas Schiper,Deian Stefan,Andrew Stone,Ryan Stutsman,David Terei,Stephen Yang,Matei Zaharia 以及 24 位匿名的会议审查人员(可能有重复),而且特别感谢咱们的领导人 Eddie Kohler。Werner Vogels 发了一条早期草稿连接的推特,给 Raft 带来了极大的关注。咱们的工做由 Gigascale 系统研究中心和 Multiscale 系统研究中心给予支持,这两个研究中心由关注中心研究程序资金支持,一个是半导体研究公司的程序,由 STARnet 支持,一个半导体研究公司的程序由 MARCO 和 DARPA 支持,在国家科学基金会的 0963859 号批准,而且得到了来自 Facebook,Google,Mellanox,NEC,NetApp,SAP 和 Samsung 的支持。Diego Ongaro 由 Junglee 公司,斯坦福的毕业团体支持。
本文经TopJohn受权转自TopJohn's Blog
深刻浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。