Replicated And Fault-Tolerant
Raft协议一种易于理解的一致性算法,比Paxos更易于理解。
Raft译文-原文地址-排版优化
Abastract 摘要
Raft是一个管理副本日志的一致性算法。一致性结果、效率和Paxos是相同的,但比Paxos更易于理解。它的构造不同于Paxos,提供了一个更易于理解、代码实现起来更容易理解的指导依据。为了提高易于理解性,它分离一致性的关键性要素,比如:领导选举、日志复制、安全性,他减少了成员状态,加强了一致性级别。改变成员之间的关系,引入了一种新的机制,覆盖集了群中大多数server来保证安全性。
1 Introduction(介绍)
一致性算法允许机器的集合作为一个紧密联系的团队来工作,即使一些机器失败了,集群正常存活提供服务。因为这个,它扮演了一个重要角色在构建可靠性、大型可扩展软件系统。
Paxos统治了过去10年一致性算法的讨论,最重要的一致性算法的实现基于Paxos或者收它影响。它也变成了课堂关于一致性算法教学的首要轮子(工具)。
不幸的是,Paxos被理解是相当困难的,尽管尝试了很多次使他变得简单易懂。进一步说,它的架构要求复杂改变来支持开发真实的系统。结果是,系统开发者与学生和Paxos苦苦做着斗争。
我们和Paxos做过斗争之后,我们决定查找一个新的一致性算法,为系统构建与教学提供一个更好的基础。我们的方法时不同寻常的,首要目标是易于理解性:我们是否可以定义一个一致性算法,为了系统实践,以一个显著简单易懂的方式来学习它。更进一步说,我们想要一个算法,使开发的直观感受更容易,易于理解对开发人员来说是必要的。算法正常的工作是必要的,知道它为什么能工作,应该是显而易见的。
我们的工作结果叫做Raft的一致性算法。在设计Raft时,我们采用了特殊的技术来提升他的易于理解性,包括分解Raft为三个关键点:leader election、log replication、安全性,减少机器的状态(相对于Paxos,Raft减少server状态的不确定性的程度不能,这种方式服务于集群中每一个机器状态的不一致性)。两个大学的43名学生参加的用户调查,表现了Raft比Paxos更易于理解:学习了两个算法之后,其中的33名学生回答Raft的问题比回答Paxos更好。
Raft和其他已经存在的分布式一致性算法在许多方面相似,最显著相同的是OKi and LIskov' Viewstamped Replication,但是它有下面几个显著的特征:
Strong leader(强领导者):Raft采用了一个更强的领导力比其他一致性算。比如:log entries 只能从leader流向其他servers,这简化了副本日志的管理,并使Raft更易于理解。
Leader election(领导选举):Raft采用一个随机定时器来进行选举。心跳机制是一致性算法所必须的,它只增加了一小部分关于心跳的机制,简单急速的解决冲突问题。
Membership changes(成员关系改变):集群中servers集合成员状态改变,Raft机制采用了一种新的joint consuensus approach(加入性一致性算法),在转移的过程中,大多数server需要覆盖两种不同的新旧配置。它允许在集群配置信息发生改变时也能正常提供服务。
我们相信Raft是比Paxos及其他一致性算法更优秀,作为教学方面与一个系统实现的基础。它是更简单的、易于理解的比其他系统;它的规范描述完全能够胜任系统实践的要求;它已经有了几种开源实现,并被使用在公司中;它的安全性已经被正式列出,并被证明;它的效率和其他算法也是可以相媲美的。
paper其他部分将会介绍Raft的其他内容,Section2 副本状态机问题,Section3 讨论与Paxos的优势和劣势,Section4 易于理解的一般性方法,Section5-8 Raft一致性协议,Section 9 Raft协议评价,Section10 讨论其他相关工作。
2 Replicated state machines (副本状态机)
一致性算法典型应用在副本状态机的上下文中。以这种方式,servers集合中的状态机计算相同状态的副本日志,即使一些servers宕机了,集群可以正常操作。副本状态机用来解决一系列默认系统容错问题的在分布式系统中。比如:大规模系统有一个的单一的集群领导,比如GFS、HDFS、RAMCloud系统,典型的使用一个分离的副本状态机来管理领导选举和存储配置信息,即使leader挂了,它也一定存活。副本状态机的典型例子 Chubby、ZooKeeper。
图1:副本状态机架构。一致性算法管理了一个副本日志,日志包括来自客户端命令的状态机命令。状态机处理相同顺序的来自logs的命令,所以,他们能够产生相同的结果。
一致性算法的首要任务是保持多台机器副本日志的一致性,副本日志是操作命令的集合日志。server中一致性模块接受来自客户端的命令,并将它写入到log中。一致性模块与其他serves的一致性模块进行通信,确保log最终包含相同顺序的相同命令,即使一些servers宕机了。一旦命令被合适的复制,每一个server的状态机都会按照log日志的顺序处理他们,并返回处理结果给客户端。结果是,servers组成了一个单一的、高可用的状态机。
一致性算法的系统实践包含典型的下面特性:
他们确保安全(从来不会返回错误的结果),在所有复杂的环境下,包括:网络延迟、系统分裂、网络包丢失、重复日志、日志重排序等。
他们是高可用的,只要集群中大多数机器是正常运作的,servers可以相互沟通消息,并和客户正常通信。因此,一个典型的5台机器的集群,容忍2个server失败。Servers假设被停机,他们将会从固定存储的log日志中恢复,并重新加入集群。
他们不会依赖时间属性,来确保日志的连续性。错误时钟和额外的消息延迟,或者更糟糕的情况,都不会造成可用性的问题。
在通常的案例中,一个命令被完成,只要集群中大多数机器已经回应了这个单一来回的远程过程调用;集群中少量的慢的机器并不会影响系统的整体性能。
3 What's wrong with Paxos(Paxos 做错了什么)
过去十年,Paxos协议已经变成了一致性协议的代名词;它成为了在课堂上交的最多的、一致性算法实现的起点的协议。Paxos首先定义了一个达成单方向一致性的能力的协议,比如单一方向的日志副本entry。我们参考这个子集作为 single-decree Paxos(单一发布命令方向)。Paxos联合多个实现这个协议的实例,来促进了一系列决定,比如log entry的写入(multi-Paxos)。Paxos保证安全性与时效性,它支持集群成员状态改变。这已经被证明是正确的,在正常情况下是有效率的。
不幸的是,Paxos有两个显著劣势。第一个劣势:它是异常的难于理解的。参看文献【15】整篇文章是众所周知的晦涩难懂;只有少数人付出了巨大努力,理解了它。结果是,做了许多次尝试使Paxos简单一点。这些解释集中在单一命令决策的子集,然而,他们还是有挑战的。在一个正式的调查中,出席NSDI2012的人员,甚至包含经验丰富的调查研究员,我们发现只有少数人对Paxos感觉到舒适。我们努力攻克Paxos;直到我们阅读了几篇Paxos简单化的解释,并设计了我们替代它协议,我们才对它有所了解。这个过程大约花费了1年的时间。
我们假设Paxos的不透明性来自它选择单法令子集作为基础。单一法令Paxos密集而微妙:分为没有简单直观解释的两个阶段并且不能独立理解。 因为这,很难就单一法令协议为何起作用产生直觉。 多个Paxos的组成规则会增加很多其他复杂性和微妙性。我们认为,达成共识的总体问题根据多个决定(即日志而不是单个条目)可以用其他更直接,更直接的方式分解明显的。
Paxos的第二个问题是,它不能为构建实际实现提供良好的基础。 原因之一是,没有针对多Paxos达成广泛共识的算法。 兰珀特的描述主要是关于单一法令的Paxos; 他勾画出了实现多Paxos的可能方法,但是缺少许多细节。 有人尝试充实并优化Paxos,例如[26],[39]和[13],但这些尝试彼此不同,也与Lamport的草图有所不同。 例如Chubby [4]已经实现了类似于Paxos的算法,但是在大多数情况下,它们的详细信息尚未公开。
此外,Paxos架构对于建立实用的系统是一个不够好的选择;这是另一个后果单法令分解。例如,独立选择一组日志条目,然后将它们融合为顺序日志,这是没有什么的好处,只会增加复杂性。更简单,更高效是在日志周围设计一个系统记录log,在该系统中以约束顺序依次添加新条目。另一个问题是Paxos使用对称的点对点的方法的核心(尽管最终表明它的形式很弱领导力,作为绩效优化)。在一个简化的世界中只有一个决定会可以实现,但是很少有实际的系统使用这种方法。如果必须做出一系列决定,更简单,更快捷是首先选举一位leader,然后让leader协调决定。
结果是,实际系统几乎没有相似之处和Paxos。每个实现都从Paxos开始,讨论了实现它的困难,然后开发了一个截然不同的体系结构。这既费时又容易出错,由于出名的Paxos加剧困难性。 Paxos的公式可能是证明其正确性定理的好方法,但是实际的实现与Paxos认为证明没有什么价值。来自Chubby实现者的以下评论是典型的:
两者的描述之间有很大的差距Paxos算法和现实世界的需求系统。 。 。 。最终系统将基于未经验证的协议[4]。
由于这些问题,我们得出结论,Paxos不能为系统构建和教学工作提供良好的基础。鉴于一致性算法在大型软件系统中的重要性,我们是否可以设计一个比Paxos有更好的性能的一致性算法,Raft是实验的结果。
4 Designing for understandability(为易于理解而设计)
我们在设计Raft时有几个目标:它必须提供完整而实用的系统构建基础,从而大大减少了设计工作量对开发人员的要求;在任何情况下都必须安全
并在典型的工作条件下可用;和它必须对常规操作有效。但是我们最重要的目标(也是最困难的挑战)是难以理解。广大观众一定有可能轻松地了解算法。另外,它必须有可能发展出关于算法的尝试,所以系统构建者可以进行实际实现中不可避免的扩展。
Raft的设计有很多要点,需要我们必须在替代方案做出选择。在这些情况下,我们根据易理解性:解释每个替代方案有多难(例如,其状态空间的复杂程度以及它具有微妙的含义?),对于一个读者能完全理解该方法及其含义吗?
我们认识到这种分析具有高度的主观性。尽管如此,我们使用了两种通常适用的技术。第一项技术是众所周知的问题分解方法:在可能的情况下,我们将问题分为几部分可以相对解决,解释和理解独立地。例如,在Raft中,我们将分离出领导选举,日志复制,安全性和集群成员改变四个主要的方面。
我们的第二种方法是简化状态空间通过减少要考虑的server状态的数量,使系统更加连贯,消除了不确定性的可能的情况。具体来说,不允许日志包含孔,Raft限制了server之间log变为不连续的方式。尽管在大多数情况下,我们消除了不确定性,在某些情况下,不确定性真实地提升了可理解性。特别是,随机方法引入了不确定性,但它们倾向于减少server状态,以一种时尚的方式处理所有可能存在的选择(“选择任何;不要紧”)。我们使用了随机化以简化Raft领导选举算法。
5 The Raft consensus algorithm(Raft一致性算法)
Raft是一种用于管理副本日志的算法,在Section2中以表单的形式展现。图2总结了算法要点作为参考,图3列出算法的关键属性;这些元素将被下面的章节分段的讨论。
Raft实现一致性算法,首先选举一位卓越的leader,然后赋予该leader管理副本日志的工作。leader接受来自客户端的log entries,将它们复制到其他服务器上,
并告诉服务器,当服务器们安全的时候,将log entries应用于他们的状态机。拥有leader简化了副本日志的管理。例如,leader可以决定从哪里放置新的entries到log中,而不用去咨询其他服务器。
数据只能以简单的方式,从leader流向其他服务器。leader可以失败,并和其他服务器失联,在这种情况下,将会选举出新的leader。
考虑到存在leader的方式,Raft分解一致性为3个相对独立的问题,他们将会在下面的章节讨论:
Leader election(领导选举):一个leader必须被选出来,当已存在leader失败之后。
Log replication(日志副本):leader接受来自客户端的log entries,并复制他们到集群中,强力使其他server的log统一leader log。
Safty(安全性):Raft重要的安全属性是状态机的安全属性,如图3所示,如果任何server添加了一个特殊的log entry到自己的状态机,然后其他server或许在相同的log index不能够添加不同的命令。Section 5.4将会讲述Raft是如何保证这条特性的;方法是在选举机制引入一个额外的机制,将会在5.2中描述。
在展现了一致性算法之后,这一节将会讨论可用性问题,和时间属性在系统中扮演的作用。
5.1 Raft basics Raft基础
一个Raft集群包含多个服务器;五是典型的数量,它系统容忍两个servers故障。在任何给定时间,每个服务器都处于以下三种状态之一:leader领导,followers追随者、candidate候选人。 在正常运行情况下,只有一个leader,而其他所有服务器都是followers。 追随者是被动的:他们不发出任何请求,只回应领导和候选人的请求。 领导处理所有客户发送的请求(如果客户端与follower联系,则follower会将请求重定向到领导)。 第三种状态,候选人,被用来选举成为一个新的领导,如第5.2节所述。 图4显示了状态及其转换过程; 转换过程将会在下面被讨论。
Raft划分时间为任意长度的term,如图5所示。terms被连续的正数所标识。每一个term以选举开始,一个或者更多的candidate尝试成为leader,在Section5.2中描述。如果candidate赢得了选举,它将作为leader服务于剩余部分的term。在一些场景election将会导致选举分裂(脑裂)。在这样的情况下,本term,在没有leader下,将会结束;一个新的term(开始新的选举)将会立马开始。Raft确保一个term最多只有一个leader被选举出来。
图5:展示了不同时间长度的term,term以领导选举为开始,选举出leader,leader将会服务于本term,直到leader失败(本term结束),一个term也可能没有选举出leader,那么将会开启新的term,开启新的选举。
不同的servers在不同的时间,跨越多个term,观察状态转移过程,在一些情形中,甚至是整个term,一些server不去观察选举。terms在Raft中作为一个逻辑时钟,terms允许servers监测过期的信息,比如一个失效的leader。每一个server存储了当前term的数字,数字随着时间单调递增。servers之间进行消息交流携带者当前term的number;如果一个server当前term number比其他servers的term number小,server将会更新自己的term number到较大的值。如果一个leader or candidate 自己的term number是过时的,它将会立即返回为follower。如果一个server接受到了一个请求带有过时的term number,它将会拒绝请求。
Raft servers进行信息交流采用远程过程调用(RPCs),基础的一致性算法只要求两种类型的PRCs,RequestVote RPCs,投票RPC在选举期间被candidate初始化(Section 5.2)。AppendEntries RPCs,扩展entries RPC在复制log entries被leader初始化,并提供了一个心跳的形式(Section 5.3)。Section7 增加了第三个RPC,在servers之间转移快照的RPC。servers将会重试RPCs在他没有收到一个及时响应的情况下,servers以并行化的方式发送RPCs,提升了性能。
5.2 Leader election(领导选举)
Raft采用心跳机制来触发领导选举。当servers启动之后,他们开始为follower。server一直保持follower状态,只要他接收到有效的RPCs来自leader or candidate。Leaders周期性地发送心跳给followers为了维持它作为领导的权威。heartbeats(不带用log entries 的AppendEntries RPCs请求)。如果一个follower在选举超时间之内没有收到交流的消息,它将假设这里没有可用的leader,并开始一个新的leader选举过程。
为了开始一个选举,follower增加它的当前的term,并转化为candidate状态。然后它为自己投票,并向集群中的servers并性地发动投票请求。candidate一直保持候选人的状态直到,发生了下面3个事情之一。(a)它赢得了选举,(b)其他server赢得选举,作为leader,(c)一段时间过去,没有winner产生。这三种情况将会在下面的章节进行单独的介绍。
candidate赢得了选举,在同一个term之内,它获得了集群中大多数server的投票。在一个给定的term,每一个server最多给一个candidate投票,on a first-come-first-served basis(note:Section5.4增加了一个额外的投票限制)。谁先成为leader,谁将为集群服务。大多数规则确保最多只有一个candidate赢得选举,在一个特殊term(the Election Safety Property in Figure3)。一旦一个candidate赢得选举,成为leader。它将会发送心跳信息给其他的服务器,来建立自己的leader地位,并阻止新的选举发生。
正在等待投票结果的同时,一个candidate或许收到了AppendEntries RPC从其他server,其他sever声明自己为leader。如果leader term(被包含在RPC请求中)大于自己的当前term,那么candidate将承认leader合法性,自己返回为follower状态。如果RPC的term小于自己的当前term,它将会拒绝RPC请求,并继续保留候选人状态。
第三种结果是,candidate既没有赢得选举,也没有输掉选举:如果多个followers在同时变为candidates,投票将会被分裂,所以没有candidate获得多数票。当发生这种情况的时候,每一个candidate将会超时,并发起新的选举,通过增加自己的term,初始化一个新的回合的选举投票。然而,如果没有额外的措施,分裂投票将会无限循环下去。
Raft采用一个随机的选举超时时间,来确保分裂投票是不经常发生的,发生了也可以被迅速解决。为了避免分裂投票的首次发生,选举超时间是一个随机的固定间隔,在150-300ms。传播到集群中的servers,在大数的情况下,只有一个server将会超时;它赢得选举并发送心跳,在其他server超时之前。相同的机制被应用在处理分裂选举的情况。每一个candidate将会重新开始它的选举超时时间,在每一个选举开始的时候,它将等待时间的流逝,在下一个选举开始之前;这减少了分裂选举的可能性,在新的选举发生的时候。Section9.3展示了这种方式快速选举出一个leader。
选举是一个例子,在考虑到设计方案的可替代性的时候。最初,我们打算使用一个等级系统:每一个candidate被分配给一个独一无二的等级,被用来选择成为candidate的时候使用。如果一个candidate发现另一个candidate用友更高的rank,它将会返回为follower状态,所以高等级的candidate更容易赢得下次选举。我们发现这种方法在可用性方法存在一些微妙的问题(如果一个更高级的server失败了,低等级的server需要超时,在次成为candidate,但是如果低等级的超时很久采用发生,这将会重置领导选举的过程)。我们对这个算法做了几次改进的尝试,每一次调整之后,角落里其他的问题就会出现。最终我们得出随机化的超时时间是更显著的与易于理解的。
5.3 Log replication
一旦领导被选举出来,他就开始服务于客户端请求。每一个客户端请求包含一个将被状态机执行的命令。leader添加命令到自己的log作为新的entry,然后并行地向每一个server发送AppendEntries请求,来复制entry到其他server的log。当entry被成功复制,leader应用entry到自己的状态机,并返回执行结果给客户端。如果followers宕机、运行慢了、网络包丢失,leader将会无限的重复AppendEntries RPCs(及时已经相应了客户端的请求),直到followers最终存储了所有的log entries。
logs被组织展示在图6,每一个log entry存储了一个状态机命令,并带有term number,当log entry被leader接受以后。其中的term number被用来检测是否连续性的在logs之前,确保一些图3中的特性存在。每一个log entry 还有一个正整数的index,用来标识它在log中的位置。
leader决定什么时候是安全的,将log entry应用到状态机的时间;这样的entry称作commited。Raft保证commited entries被持久化的,最终将会被执行在所有的可用的状态机中。一旦leader创建了entry,并被复制到大多数机器中,称这个log entry 被commited(图6中的entry7(index为6)已经被commited)。图中还提交leader的先前的log entries,包含先前的leader创建的log entries。Section5.4讨论了,当leader发生改变之后,被commited log entry 这个规则下的,发生的一些微妙的问题,然后解说了commitment的正确性。leader追踪了它所知道的将会被提交的最新的index,还包含了将来的AppendEntries RPCs(including heartheats),以便以其他servers最终能够找到。一旦followers知道log entry被提交,它将会应用这个entry到自己的状态机中(以 log中的entries顺序)。
leader 接受到客户端命令,发送命令到各个follower,follower接受相应,返回结果给leader,leader commit这个客户端命令,然后发送消费给follower,这个命令被commit了,follower commit这个命令。
我们设计的Raft log机制在不同的servers的log之间维护了log的高连续性。高连续性不仅简化了系统行为和系统是可预测的,是保持系统安全性的重要组成部分。Raft维护了如下两种特性:这两种特性组成了Log Matching特性,如在第三幅图片中描述的特性。
在不同logs的拥有相同index与item的entry,他们存储了相同的命令;
在不同logs的拥有相同index与item的entry,在这个entry之前的所有log的entry是相同的;
leader在一个log index的term最多创建一个entry,log的entries从不会改变他们的位置,在这样的情况下,第一条特性追随这样的事实;通过一个简单的AppendEntries一致性检测来保证第二条特性的实现。当leader发送AppendEntries RPC时,会带上新entry的先前的entry,及entry的term、index;如果follower不能发现先前的log entries,将会拒绝这个AppendEntries RPC请求。一致性检测扮演了一个归纳的步骤:当logs进行扩展新增的时候,初始化的空状态的logs满足了 log Matching特性。作为一个结果,无论何时AppendEntries返回成功,leader知道followers的log和自己的log是相同一直的,直到新的entries位置。
在正常操作之间,leaderf与follower的logs保持一致性,所以AppendEntries一致性检测从不会失败。如果leader宕机了时logs的一致性不在保持(old leader的entries没有被完全被复制它自己的log中)。这些不一致性加剧一系列leader和followers的宕机性。图7阐述一种followers的日志不同于新leader的方式。一个follower或许错过了leader log中的一些entries,一些额外的entry丢失,或许没有出现在leader、followers中。错过的或额外的entries在log中有可能跨越多个items。
图7:当一个server成为leader,followers会出现上图的(a-f)种情形。每一个方格代表一个log entry;每个方格中的number代表着它的term。一个follower或许丢失entries(a-b),也获取有一些额外的未被提交的entries(c-d),或者同时存在的(e-f)。举个例子,情形7为在term2、term3时分别f server自己成为leader,并迅速宕机,因此与其他server的log不一致。详细过程,在term2自己成为leader,并添加了3个entries到自己的log中,在commit这3个entries之前,宕机了;然后迅速重启,在term3成为leader,又添加了一些额外的entries到自己的log中;在term2、term3期间,任何entries commit之前,leader自己又再次宕机了并在几个term中一直为恢复。
在Raft中,leader通过强制followers赋值自己的log来保持一致性。这个意味着在follower的log中冲突的entries将会被leader log中的entries覆盖重写。Section 5.4 将会详细介绍它的安全性,在一个或多个限制的条件下。
leader为了保证自己log同步给follower log的一致性,leader必须找到最新的leader和follower达成一致性的log entry,删除这个entry之前的follower的所有log entries,leader需重新发送这个entry之前的所有entris。follower与leader的log一致性,由一致性检测来保证的。所有这些动作发生在在一致性检测之前,通过AppendEntries RPCs实现。leader为每一个follower维护了一个nextIndex,nextIndex是将要发送给follower的leader log entry index数据。当leader当选了之后,它初始化所有的nextIndex值,使用自己log的最后一个值(图7 index11);如果一个follower log 不同于leader log,AppendEntries 一致行检测将会失败,在它下次进行 AppendEntries RPC。一次拒绝之后,leader减少nextIndex值并在此尝试AppendEntries RPC。直到最后nextIndex达到leader与follower的log匹配点。当这个发生的时候,AppendEntries RPC将会成功,移除follower所有冲突的entries和添加来自leader的新的entries。一旦AppendEntries成功,follower log将会和leader log保持一致,然后以这种方式一直保进行下去。
(followers的log的entries必须和leader的一致,不一致的leader会通过AppendEntries RPC覆盖重写followers的log entires,直到和leader的log匹配成功为止。)
(优化:如果有需求,可以优化减少AppnedEntries拒绝的次数。例如,当拒绝一个Append RPC,follower可以包含这个term的冲突的entry和这个term存储的第一个index。采用这个优化,leader可以减少扫描这个term中的冲突的entries的nextIndex。这样,一个entries冲突的term只需传输一次AppendEntries请求。在实践中,我们怀疑这个优化是否有存在的必要,因为失败发生的并不频繁,然后也不像那样有许多不不一致的entries。)
采用一致性检测机制,leader并不需要额外的动作来重新存储log consistency,当它成为leader之后,leader开始正常的操作,logs自动收敛来应答AppendEntries一直检测失败。leader从不重写或删除自己log entries(the Leader Append-Only Property in Figure 3)。
log 副本机制展示了预期一致性特性,如 Section 2:Raft可以接受、赋值、应用一个新的 log entries,只要绝大多数server同意;在一个正常的情况下,一个新的entry通过一个单一回合RPCs到达集群中的大多数,一个单一的反应慢follower将不会影响集群的性能。
5.4 Safety
前面章节描述了Raft如何进行leader选举和log entries复制。然而上面描述的机制远不够满足确保每一个状态机准确地以相同的顺序执行相同的命令。比如:a follower或许是不可用的,当一个leader正在commits几个entries,然后follower被选举为leader,并传递了一些新的entires,覆写了follower log entries;作为一个结果,不同的状态机或许执行不同的命令顺序。
这个section通过增加一个server选举为leader的限制完善了Raft算法。限制保证成为leader在任何一个给定的term必须包含这个term之前的所有commited log entries(the Leader Completeness Property from Figure 3)。考虑到选举限制,我们将会将更细致的讨论commit rule。最后,我们将展现一个粗略的领导选举完整性的框架,并展示了leader如何正确引导副本状态机的行为。
5.4.1 选举限制(Election restriction)
在任何以领导选举为基础的一致性算法中,leader必须最终存储所有commited log entries。在一些算中,比如 Viewstamped Repliaction,a leader 可以被选举,即使最初没有包含所有commited entries。这些算法包含额外的机制来表示丢失的entries,并转化log entries 到新的leader,既包括选举过程的时候,还有log entry短暂落后的情况。不幸的是,这将引入相当大的额外的机制与复杂性。Raft采用了一个简单的方法,保证了先前term commited log entries出现在刚刚通过新选举出的leader log中,而不需要转移这些entries到leader。这意味着log entries 只能向一个方向流动,从leader复制到followers,leaders从不会覆写它的已经存在的logs。
Raft使用投票流程阻止candidate server赢得选举,除非它包含所有commited entries。一个candidate server必须联系集群中绝大多数server,这个行为意味着每一个commited entry 必须至少出现在集群中一个server上。如果 candidate server log 至少是最新的和集群中其他server相比。然后它将会持有所有commited entries。RequestVote RPC 实现了这个限制:RPC 包含了candidate server log 信息,voter server 将会拒绝投票,如果voter server 自己的 log 比candidate server 的log 更新。
up-to-date:Raft如何决定两个logs哪个是更新的呢?通过比较log中最新的entries的index和term,如果logs 最新的entries 所属不同terms,拥有更新term的log是比较新的一个log。如果logs拥有相同的term,哪个log更长一些,哪个log是更新的。
5.4.2 提交上一个term的entries(Commiting entries from previous terms)
正如 Section 5.3所描述的,leader 在当前term知道一个entry是否被commited,commited 代表着entry已经被存储到集群中大多数机器,并发送投票结果给leader,leader确认这个entry是commited。如果一个leader在commiting entry之前宕机,将来新的leader将会尝试完成这个entry的复制到followers工作。然而,leader不能够立即得出结论,这个entry是否被先前term commited,曾经这个entry被存储到集群中多个servers。图8阐述了一种情景,一个old entry被存储到大多数的servers中,还是被新来的leader覆写了这个entry。
一个时序图展示了为什么leader不能够决定老的term的正在提交log entries。
在图(a)S1是leader,leader复制了index2到部分server中;
在图(b)S5在term3被选举为leader,通过S3、S4、还有它自己,在index2接受了一个不同entry。
在图(c)S5宕机,S1被重新选举为leader,leader继续entries复制。在这一时刻,term2时期已经将log entry复制到了大多数机器中,但是并没有被commited。如图(d)如果S1宕机,S5可以被选举为leader(通过S2、S3、S4的选举),并从term3将自己的log entry覆写到其他followers。然而,如果S1在当前term将entry复制到大多数机器中,在它宕机之前,如图(e),然后这个entry被commited(S5不可能赢得选举)。在这一点之前的所有entries也将同样被commited。
为了图片8中这样的问题,Raft从不通过计算副本数来提交先前term中log entry。只有当前term的leader通过计算副本数来进行commited判断。副本数大于当前机器数量的1/2,即可进行commited。一旦当前term一个entry已经使用这种方式进行提交,这个entry之前的log entries也会直接被commited,因为Log Matching 特性。这里有一些情景,leader可以安全的推断出older log entries 是否被commited(比如,如果这个entry已经被存储到每一个server中),但是Raft为了简单性采用了一个更保守的方式。
Raft在提交规则中招致了一个额外的复杂性,因为当一个leader从先前的terms复制log entries时,log entries 带有所属原始term的数字。在其他一致性算法中,如果一个新的leader复制先前term的entries,它必须带有它的新的”term number“。Raft方法关于log entries更易于解释,既然他们维护了相同的term number,跨越周期、跨越logs。其他情况,Raft的新leader发送先前term的一些log entries 通过其他算法。(其他算法必须发送多与的log entries 来重新编号他们,在他们被commited之前)。
5.4.3 Safety Argument(安全性讨论)
第一个讨论的安全性是leader Completeness的问题,Raft采用反正法证明了一个entry(d) commit在当前term(T)中,假设后面的term(U)不存在这个entry(d),term(U)>term(T);通过反证法证明,entry(d)也必将出现在term(U),所以存在矛盾,从而entry(d)也必将存在于term(U)。
第二个安全性是状态机的安全性问题,状态机的安全性是指,多个状态机必须经过相同的运算,得出相同状态的结论。一个server将指定的index entry应用到状态机,没有server将会把在相同index的不同的entry, 应用到自己的状态机中。server应用到状态机的entry,这个entry及之前的entries,必须是在leader log 中提交的。
状态机的安全性,依赖两点:一是不同server间的log entries是相同的,在以后的term中log entries也是相同的(由日志匹配原则与领导完整性来保证),二是状态机按照log的index顺序执行log entries命令,从而保证了状态机安全性。
5.5 Follower和candidate宕机
到目前为止,我们一直在讨论leader的失败。follower与candidate的失败比leader失败处理更简单,follower和candidate失败的处理方式相同。当follower或candidate宕机,发送给它的RequestVote与AppendEntires RPCs将会失败,Raft通过不确定地重试发送消息来处理这些失败。如果宕机的server重启,RPC将会成功地完成。如果一个server在本机处理完RPC,在给leader发送response的时候发生了宕机。在server重启之后,它会leader的重复的RPC。Raft RPCs是幂等性的,所以不会造成损害。比如:follower收到了一个已经存在与自己log的AppendEntries请求,它将会忽略这些entries在新的请求中。
5.6 时间计划性和可用性
Raft安全性的一个要求是他不依赖与时间计划性:系统不能够刚刚好产生正确无误的结果,因为一些会发生的比预期的比较快或者比较慢。(时间计划性是指:不是按照定时的方式,进行活动,比如leader选举,活动是随机进行的)。然而可用性(系统及时相应客户端请求的能力)必然依赖于时间。比如:如果消息交换花费的时间长于典型的servers之间的宕机时间,candidate将不能够有足够的时间来赢得选举,如果没有一个稳定的leader,Raft将不能够正常工作。
leader选举是一个Raft时间应用的一个重要方面。Raft将会能够选举与维护一个稳定的leader,
只要满足下面的时间不等式:
broadcastTime << electionTimeout << MTBF
broadcastTime:广播时间,一个server并行地发送RPCs到集群中每一个server并接受servers返回结果的平均响应时间。
electionTimeout:领导选举超时时间。
MTBF:Mean Time Between Failures 平均故障时间。一个服务器发生两次失败的平均间隔时间。
广播时间应该少于领导选举时间,这样leader才能可靠地发送心跳信息。考虑到随机的方式设置领导选举时间,这个不相等性,也避免了选举分裂的发生的可能性。选举超时时间应该少于MTBF,所以系统才能稳定的运行。当leader宕机,系统大概在选举超时时间之内不可用,我们希望这占用总共时间的一小部分时间。
广播时间、MTBF依赖于系统属性,选举超时时间我们必须做出选择。Raft RPCs要求接受服务器持久化信息到稳定存储中,所以广播时间大概分布在05.ms到20ms,依赖于存储技术。作为一个结果,领导选举超时时间应该分布在10ms到500ms。典型server的MTBF是几个月或者更长的时间,很容易满足时间计划性要求。
6 Cluster membership changes (集群成员改变)
直到现在为止,我们假设集群的配置是固定的,配置是指一致性算法中servers的参与者,集群中的server的数量是固定的。实际中,集群server的数量偶然发生改变是必须的,比如说替换掉失败的server,以及改变副本数量。即使这个可以在集群下线的时候来操作,更新配置文件,然后重新启动集群,这将会造成集群不可用在这个期间。额外的,如果由人来操作,将会带来操作失败的风险。为了避免这些问题,我们我们决定自动装备配置文件与融合他们到Raft一致性算法中。
为了保证配置改变的安全性,在一个term期间,不能有两个领导被选举出来。不幸的是任何从老的配置更改到新的配置都是不安全的。那是不可能的一次性自动切换所有的severs,所以集群在转移期间,可能分裂成两个无关的大多数机器。如图10:
图10:直接从老的配置切换到另一个配置是不安全的,因为severs将会在不同的时刻进行切换。图中,集群中server数量从3个变为5个。不幸的是,在某一时刻的某一点,在同一个term,将会选举出两个不同的领导。一个代表老配置的投票的大多数,一个代表新配置的大多数。
为了确保安全性,配置的改变必须使用两个阶段的方式来解决,这里有一系列的方法来实现两阶段协议。比如,一些系统第一阶段使老配置失效,所以它也不处理客户端的请求。然后第二阶段启动新的配置。在Raft集群中,第一步切换到一个过渡的配置,我们叫做joint consensus(加入集群一致性);一旦加入一致性被提交,系统将会转移到新的配置。加入集群一直性将会联合老的和新的配置:
log entries将会复制到集群中所有的机器,在两个配置都复制。
任何一个server,在两个配置中,都有可能成为leader。
共识(选举或者entry commitment)在两个配置中,分裂的大多数中都要达成。
joint consensus,在不妥协安全性的情况下,允许单独的server在不同的时刻,进行配置的转化。进一步说,贯串整个配置改变的过程,joint consensus允许集群处理客户端发送的请求。
图11:集群配置发生改变的时序图。虚线展示了配置entries已经被创建,但是没有被提交。实线代表着最新被commited配置entries。leader一开始创建了C(old,new)entry在它自己的log中,然后提交C(old,new)entry到C(old,new)中,C(old,new)代表:Cold的大多数server,Cnew大多数的server。然后他将创建Cnew entry 和提交它到Cnew的大多数server中。在任何时刻,Cold与Cnew都没有独自的做出决定的机会。
Cold单独做抉择的截止时间为C(old,new)被提交之前;Cnew单独做抉择的最早时间为Cnew被创建的时候,这两个时间点不存在交集,所以不会选出两个leader。
在C(old,new)状态的配置,不论这个C(old,new)是否被提交,这个状态的领导选举,必须收到Cold配置的majority 和 Cnew配置的 majority的统一,才能提交或选出leader。这个过渡状态解决了,old、new 配置状态不一致,选举出多个领导的问题。
集群配置采用特殊的entries进行交流,并被存储在副本日志中;图11阐述配置发生改变的过程。当leader收到一个从Cold到Cnew配置改变的请求,将新配置作为一个log entry存储到C(old,new)中,并采用副本机制将它存储到其他servers。一旦server添加新的配置到自己的entry log中,server将会采用新的配置作为将来决策的依据(server总是采用最新的entries,无论这个entry是否被提交)。当C(old,new)配置被提交,leader将会采用C(old,new)来作为决策的规则。如果leader宕机,一个新的leader将会从Cold或者C(old,new)配置中被选出,取决于赢得选举的candidate是否已经接受到C(old,new)配置。在任何例子中,Cnew将不会单方面的做出决定在这期间(C(old,new)被提交之前)。
C(old,new)被提交之后,无论Cold、Cnew做决策的时候,都没有了对方的支持,Leader完整性确保只有带有C(old,new)配置的servers才能被选举出作为领导。现在是安全的,对于leader来创建新的Cnew配置,并将它复制到集群中。同样地,日志会在servers接收到它的那一刻起生效。在Cnew的规则下新的配置被提交,老的配置将会变得无关,没有在新配置的servers将会被停机下线。如图11展示,没有Cold、Cnew单独做出决策的时刻;这确保了安全性。
这里还有3个直达配置的问题。第一个,新加入集群的servers没有初始化的log entries。如果他们以这个状态加入集群,将会花费一段时间类弥补他们落户的log entries,在这段时间之前,集群或许不能够commit新的log entries。为了避免可用性的不连续性。Raft在配置改之前,引入了一个新的阶段,在这个阶段新加入的servers不能作为投票的成员(leader 复制log entrie到他们,但是不考虑他们为投票的大多数)。一旦新的servers log entries追赶上集群的其他servers,配置将会按照前面讨论进行成员状态的改变。
第二个问题是集群领导或许不在新加入的servers中。在这个案例中,一旦leader commited Cnew log entry,它变下线变为follower。代表着在commiting Cnew这段时间内,leader正在管理着一个不包含它自己的集群。它复制 log entries到其他servers,但是投票的大多数servers并不计算它自己。当Cnew被commited,leader即将发生转移,因为在这个时刻新的配置能够独立地做出决定(以后总是从新的配置中选举leader成为可能)。在这个时刻之前,大多数的情况是leader只能从Cold中选举产生。
第三个问题是移除不在Cnew配置的servers会中断集群。这些servers不会受到心跳,他们将会超时并发起新的选举。他们会发送带有新的term的RequestVote RPCs,这会导致当前leader重新变为follower。leader最终会被选举出来,但是这些需要移除的server已久会超时,重新进行选举过程,导致集群的低可用性。
为了避免这个问题,servers可以忽略投票请求,当他们相信当前leader存在时。特殊的,server在当前leader的最小超时时间之内,收到一个RequestVote RPC,它可以不去更新当前自己的term和授权这次投票。这不会影响正常的选举,每一个server在开始选举之前,会等到最小的选举时间超时。然而,这避免了需要移除的servers的打扰:如果leader能够正常发送它的心跳向集群,它将不会被更大的term numbers罢免。
7 Log compaction (日志压缩)
Raft在正常工作期间处理了很多客户端请求,并将写入到logs,在一个真实的系统中,logs是不能无限制的增长的。只要log增长的足够长,它将会占用更多的空间,花费更多的时间来同步logs。如果没有机制丢弃废弃的消息,最终会导致系统不可用的问题。
快照是最简单压缩方式。在快照中,当前系统状态的的entry将会写入在存储层的快照中,然后当前状态之前的log entries将会丢弃。快照被应用在Chubby和ZooKeeper。
增量化的日志来压缩方式,如使用 log cleaning、log-structured merge tress 结构化合并树都是可行的。这些操作一次性被应用在少量的数据集上,所以从始至终,他们能够平稳的开展压缩操作。他们首先会选择一个积累了好多被删除、被重写对象的数据区域。接着重写该区域还存活的对象使之存储的更加紧凑,接着释放该数据区域。这需要额外的有显著效果的机制、还有复杂的和快照对比方式,这将简化全量数据操作的问题。虽然日志的清除对Raft做了改变,状态机能够使用相同的接口实现LSM trees作为快照。
图12:server压缩已经提交的indexs1~5 entries采用一个新的快照。在这个图片中它存储了当前状态变量x与y的值。快照中存储了最后的index与term,方便接下来的index6被加入到快照。
在图12中展示了Raft基本的快照理念。每一个server单独存储自己的快照,快照中包含了已经提交的log entries。大部分的工作是写当前server的状态到快照中去。Raft的快照还包含一小部分元数据:last included index 是提交到快照中最后一个log entry的index,last included term是这个entry所在的term。元数据被保留下来为了支持AppendEntries consistency check,确保跟随快照中第一个log entry,因此他们entry需要先前的log index与trem。为了集群成员的改变,快照包含了log中最新的配置,作为last included index。一旦server完成了快照的书写,它将会删除last included index之前所有的log,还有之前的快照。
即使servers正常地独立地开启快照,leader必须偶尔发送快照给落后的follower。当leader已经丢弃了下一个entry,一个需要发送给follower的entry。幸运地,这种情况在正常操作中是不可能发生的:follower一直跟随着leader,follower已经包含了这个entry。然而,一个意外慢下来的follower或者新加入集群的server将不会跟上leader,leader发送一个快照给follower,使follower跟随上最新的log entry。
图13:一个InstallSnapshot RPC的汇总,快照将会被分成多份进行发送;发送给follower的每一份快照带有一个生命周期的标志,所以可以被选举计时器重置。
leader发送一个快照给落后的follower,称作InstallSnapshot RPC,图13。当follower接收到一个快照,它必须决定自己已经存在的log entries如何处理。通常情况下快照将会包含follower不含有的信息。在这个例子中,followerer丢弃了整个log;他们将会被作废,可能包含还没有被提交的与快照发生冲突的entries。相反地,如果follower接收到落后与自己的log entries的快照(由于转播或者错误的原因),快照中的log entries将被删除,但是快照后面的log entries将是有效的,并一直被保留。
快照方法违背了领导强一致性原则,因为followers可以接受快照,在leader没有同意的情况下。然而,我们认为这个背离是合理的。因为在达到一致性方面,leader已经避免了冲突的决定,当形成快照时,一致性已经被达成。所以,没有决定冲突。数据依旧是从leaders流向followers,只需要followers能够识别他们发送的数据。
我们考虑一个以leader为基础的替代方案,只有领导能够创建快照,然后发送快照给每一个follower。然而这有两个劣势,第一:发送快照给每一个follower将会浪费网络带宽,并降低快照的处理速度。每一个follower都有自己的快照信息需要处理,然后,处理自己的快照,明显比处理接受到网络发送来的快照成本更低。第二:leader的实现会变的复杂。比如:leade需要并行地发送新的log entry给每一个follower,而不去堵塞处理客户端发来的请求。
这里有两个更严重影响磁盘新能的问题。第一个:servers必须决定何时做快照。如果太频繁将会浪费磁盘带宽和能量。如果不频繁,将会浪费磁盘的存储能力,如果机器重启,增加了重新处理日志的时间。最简单的方式,是以一个固定的大小来处处理快照。如果快照大小,明显大于期望的快照大小,超过磁盘带宽部分的快照将会变小。
第二个性能问题是:写快照会花费显著的时间。我们不想因为这个来延迟正常的操作。解决方案是写时复制技术,新更新的entry将会接受,也不会影响写入快照。比如:状态机可以构建一个自然支持这个的函数式数据结构。可替代的是,操作系统写时复制技术的支持,它可以被用来创建一个状态机的内存快照(我们是采用这个方式实现的)。
8 Client interaction 客户端交互
这一节将描述客户端利用Raft如何进行交互,包括客户端如何找到集群领导和Raft如何支持线性语义。这些问题已经在所有一致性为基础的系统被解决,Raft的解决方案与其他系统类似。
Raft的Clients发送所有的请求到leader。当一个client启动之后,它会选择一个随机的server。如果client的第一个选择不是leader,server将会拒绝client的请求,然后返回给自己所知道的最新的leader(AppendEntries请求包含leader的网络地址)。如果leader宕机,客户端请求将会超时,客户端将会重选随机选择一个server。
我们的目标是实现一个线性的语义(每一个操作应该立即被执行,有且仅有执行一次,在它的调用和响应的时候)。然而Raft会执行一个命令多次:比如,leader宕机在commiting the log entry之后,响应客户端请求之前,client将会重新发送命令给新的leader,造成它将会被执行两次。解决方案是为客户端的每一个请求分配一个独一无二的序列号。然后,状态机追踪最新的每一个客户端已经处理的序列号,然后相应客户端。如果它收到收到一个命令带有已经被执行序列号,将会立即相应这个请求已经被执行了。
只读操作的线性语义,只读操作可以被处理而不用写任何东西到log中。然而,如果没有额外的措施,这将会有返回过期数据的风险,因为一个leader响应着一个请求,在它不知情的情况,它已经被废弃了,产生了新的leader。线性读必须返回非过期数据。Raft在不使用log的前提下,需要两个额外的措施,来保证数据的时效性。第一,leader必须包含最新的被提交的entry,Leader Completeness Property 保证了leader含有所有commited entries。但是在一个term的开始,让或许不知道他们是哪些。为了找出他们,需要提交一个entry在当前term。Raft处理这个,每一个leader commit 一个空白的 no-op entry 到自己的log中,作为这个term的开始。第二:leader必须检测它是否被免职在处理一个只读请求之前(如果最近leader发生了改变,它的信息或许是过时的)。Raft通过一个leader和集群中大多数server进行心跳信息交换,在处理一个只读请求之前。替代方案是,leader可以依赖于心跳机制提供一个租约的形式,但是这将依赖于时间的安全性(它假设了时钟抖动的有限性)。
9 Implementation and evaluation(实现与评估)
我们实现了Raft协议作为副本状态机的一部分,副本状态机存储了RAMCloud的配置信息,作为RAMCloud故障切换的coordinator(协调者)。Raft实现包含了大约2000行C++ code,不包含测试、注释、空白行。源代码是可以得到的。这里还有25种独立的不同开发程度的、三方的开源代码实现,都是基于Raft paper 实现的。与此同时,各种公司正在开发基于Raft的系统。
接下来的章节将会在三个方面评估Raft协议:易于理解性、正确性、表现性能方面。
9.1 Understandability 易于理解性
为了评估Raft相对与Paxos的易于理解性,我们做了一个实验研究,参与实验的是斯坦福高级操作系统课程和伯克利分布式系统课程的高年级已经毕业学生的或未毕业的大学生。参与者表现出Raft的更易于理解。
9.2 Correctness 正确性
我们已经开发了正式的文档说明,与第五章节所描述的一致性算法的安全性证明。正式的文档总结如图2所示,更完整细致的采用了TLA+标准语言描述。
9.3 性能表现
Raft性能表现与Paxos等其他一致性算法相同。最重要的性能体现是当leader已经被选举出来之后,复制log entries到followers的时间。Raft采用了最小数量的messages(一个单一的旅程来回leader传递entry到followers中一半的多数即可)。将来提升Raft的性能是可行的。比如:非常简单地支持批量与管道请求,为了更高的吞吐量与低延迟。各种优化已经在其他算法中提出;好多优化可以被应用到Raft,但是我们留下了这部分工作将来来完成。
使用我们自己的Raft实现来测试Raft领导选举算法的性能,并回答2个问题。第一个:领导选举是过程否能迅速收敛?第二个是:leader宕机之后,选出新的leader之前,最小的故障停机时间?
为了测试leader选举,我们重复地宕机一个五个server集群的leader,并测试从宕机到选举一个新leader的时间。为了测试更糟糕的情况,在每一次的测试中,servers含有不同长度的log,所以一些候选者没有资格成为leader。更进一步,为了鼓励脑裂的发生,在leader终止它的处理过程之前,我们的测试脚本触发leader的一个同步的心跳RPCs广播(这个操作很像在leader宕机之前正在复制一个新的 log entry)。在心跳间隔内,leader被均匀的随机宕机,宕在所有的测试中,宕机时间为最小选举超时时间的一半。因此,最小的可能宕机时间为最小的领导选举超时时间一半。
图16上半部分图,横坐标为领导选举花费的时间,划线的值为领导选举超时时间。展示了领导选举超时随机时间的增加,大大降低了选举所花费的时间。
领导选举超时时间加上一个小量的随机数时间,是能够避选举脑裂的产生的。在选举超时时间缺少随机时间的情况,领导选举持续花费了10秒,在我们的测试实验中,因为有选举脑裂的产生。只仅仅增加5ms的随机领导选举超时时间,效果是显著的,导致平均宕机时间变为287ms。增加随机时间提升了花费更多选举时间的糟糕情况:在随机时间为50ms的例子中,选举花费平均时间为513ms。
图16下半部分这个图,横坐标为选举领导花费时间,划线值为领导选举超时时间。展示了宕机故障时间减少可以通过减少领导选举的超时时间。如:领导选举超时时间为12-24ms,它平均只花费了35ms在选举一个新leader时(最长的时间花费了152ms)。然而,在防止产生选举脑裂的前提下,降低领导选举超时时间:时间太短,leaders将会有传播心跳信息的困难,而其他servers开始了新一轮的选举。这回造成不必要的leader改变和降低整个系统的可用性。我们强烈建议领导选举超时时间为150-300ms;这样的时间不会造成不必要的领导改变和好的系统可用性。
10 Related work 相关工作
内容不重要,未翻译。
11 Conclusion 结论
算法经常将正确性、效率、简明作为设计的首要目标。他们是有价值的目标,我相信易于理解性同等重要。只有开发人员将算法开发为可以实现的系统,首要目标才能实现,算法的实现避免偏离与超越描述算法的出版物。除非开发人员对算法有深入的理解和创造算法时间的直接,否则那将是困难的在算法实现中保留,算法文献渴望的属性。
在本论文中我们直达分布式系统一直性问题,它已经被广泛接受,但是还没有被实现的Paxos算法,Paxos算法给学生和开发者带来了挑战很多年。我们开发了一个新的算法Raft,它展现了比Paxos更更容易理解。我们相信Raft为系统开发构建提供了一个更好的基础。使用易于理解性作为首要目标的方式,改变了我们设计的方式;随着系统的设计,我们发现我们重新复用了一些技术,比如分解问题与简化server状态空间。这些技术不仅提升了Raft的易于理解性,同时更容易地说服我们自己这个是正确的。
12 Acknowledgmets 致谢
内容不重要,未翻译。
关键点:
1. 将一致性算法拆分为领导选举、日志复制、安全性、成员状态改变,等几个相互独立的小问题。
2. 通过减少server的状态个数,来简化成员状态改变的问题。
3. log 日志不允许不连续,log entry 的流向,只能从leader 流向其他servers。
4. Strong leader(强领导者):,Raft采用了一个更强的领导力比其他一致性算。比如:log entries 只能从leader流向其他servers,这简化了副本日志的管理,并使Raft更易于理解。
5. Leader election(领导选举):Raft采用一个随机定时器来进行选举。心跳机制是一致性算法所必须的,它只增加了一小部分关于心跳的机制,简单急速的解决冲突问题。
6. Membership changes(成员关系改变):集群中servers集合成员状态改变,Raft机制采用了一种新的joint consuensus approach(加入性一致性算法),在转移的过程中,大多数server需要覆盖两种不同的新旧配置。一旦joint consuensus approach 新旧配置状态都被提交之后,才可以向新配置的状态进行转移。它允许在集群配置信息发生改变时也能正常提供服务。
7. 他们确保安全(从来不会返回错误的结果),在所有复杂的环境下,包括:网络延迟、系统分裂、网络包丢失、重复日志、日志重排序等。
8. 他们是高可用的,只要集群中大多数机器是正常运作的,servers可以相互沟通消息,并和客户正常通信。因此,一个典型的5台机器的集群,容忍2个server失败。Servers假设被停机,他们将会从固定存储的log日志中恢复,并重新加入集群。
9. 他们不会依赖时间属性,来确保日志的连续性。错误时钟和额外的消息延迟,或者更糟糕的情况,都不会造成可用性的问题。
10. 在通常的案例中,一个命令被完成,只要集群中大多数机器已经回应了这个单一来回的远程过程调用;集群中少量的慢的机器并不会影响系统的整体性能。
11. Safty(安全性):Raft关键安全属性是状态机的安全属性,如图3所示,如果任何server添加了一个特殊的log entry到自己的状态机,然后其他server或许在相同的log index不能够添加不同的命令。
12. term作为Raft的一个逻辑时钟,使它不在依赖于机器的绝对时间,摆脱了对时间的依赖,更方便的进行选举、信息交换实效性问题等操作。
13. RPC Request:分为
投票 Request Vote RPC
增加副本日志条目与充当心跳的Append-Entries RPC
servers之间转移快照的RPC
14. Leader election 领导选举的三种结果:
自己胜出成为leader
别人胜出,别人成为leader
分裂选举,一直无限期进行选举下去
15. Log replication日志复制:
leader正常管理日志复制
日志的commited操作,及状态机执行entry中的command
一致性检测,保证follower和leader log entries 相同
当follower与leader 出现log entires不一致情况,根据leader log entris强制推送给follower,follower与leader日志一致性点的确认,由一致性算法来确认。
如果两个entries在不同的logs有相同的index与term,然后这个entry之前的,两个log的entries是相同的,是由一致性检测来保证的;当leader发送AppendEntries RPC时,会带上新entry的先前的entry,及entry的term、index;如果follower不能发现先前的log entries,将会拒绝这个AppendEntries RPC请求,如果请求返回成功,就代表follower的log和leader的log一致。
当leader发生变化时,leader的log entris与follower的log entris是不相同的;出现这种情况,leader强制follower同步自己logs,这样是安全的 。当follower的log entries与leader的entries不相同时,需要确定哪个位置的entry是相同的,然后删除follower这个位置之后的所有元素,使用leader传输的新的log entries。这个位置的确定是由一致性检测来查找的,leader保存了将要发送给每一个follower的nextIndex,这样leader就发送这个元素到follower,如果follower的上一个位置的log entry不相等,follower将会拒绝这个请求,直到follower确定到和leader相同entry的位置。
16. leader选举限制
领导选举的限制,任何成为leader的server,必选包含所有commited log entries。投票选举过程实现了这个这个限制,想要成为leader的candidate必须包含最新的log entries,这也意味着它持有所有commited entries;RequestVote RPC包含candidate的log的term、index;如果follower比candidate的log entry新,那follower将拒绝投票,并成为新的candidate。
日志的流动只能向一个方向流动,从leader流向follower。
leader提交先前term的log entries,leader提交当前term的log entries,通过判断entry是否被存储到大多数的机器中,这种方法不能用于先前term的log entries是否被被提交。Raft处理先前term的entries时,首先复制先前term的log entries,entries 带有先前term的term number,然后再投票判断先前term的 entries是否被提交。
领导选举期间,集群是不可用的,是不对外提供服务的,大约150-300ms。
17. Follower和candidate宕机
follower、candidate宕机之后,leader一直会发送AppendEntries请求给server,server处于非应答状态;直到server重启,重新响应AppendEntries请求,server处理AppendEntries是幂等性的,不用关心请求是否重复;幂等性是通过server的log entries的term、index来实现的。
18. Safety Argument(安全性讨论)
第一个讨论的安全性是leader Completeness的问题,Raft采用反正法证明了一个entry(d) commit在当前term(T)中,假设后面的term(U)不存在这个entry(d),term(U)>term(T);通过反证法证明,entry(d)也必将出现在term(U),所以存在矛盾,从而entry(d)也必将存在于term(U)。
第二个安全性是状态机的安全性问题,状态机的安全性是指,多个状态机必须经过相同的运算,得出相同状态的结论。一个server将指定的index entry应用到状态机,没有server将会把在相同index的不同的entry, 应用到自己的状态机中。server应用到状态机的entry,这个entry及之前的entries,必须是在leader log 中提交的。
状态机的安全性,依赖两点:一是不同server间的log entries是相同的,在以后的term中log entries也是相同的(由日志匹配原则与领导完整性来保证),二是状态机按照log的index顺序执行log entries命令,从而保证了状态机安全性。
19. 算法实践特性
安全性:在复杂的条件下也能返回正确的结果,比如:网络延迟、被分开、包丢失、复制错误、命令重新排序等。
容错性:集群中大多数机器能和客户端通信,就能对外提供服务,5个机器,3个机器正常,就能保证集群的可用性;损坏的机器可以从状态存储中恢复、并重新加入集群。
不依赖时间线来保持副本日志的一致性,即使始终混乱、消息延期,机器可用性问题。
在大多数场景中,服务端集群处理客户端的请求,只需要集群中大多数机器返回响应即可,并不需要等待反应慢的机器机器返回结果,并不需要等待响应慢机器的返回结果,因此响应慢的机器不会拖慢整个集群的性能。
Paxos 缺点
1. single decree 是它的基础,但是这个是凭直觉理解的东西
2. 它的单一 single decree,合成多个 multi decree 是复杂的
3. 它只是一个一致性算法的理论基础,并没有阐明一些具体的实现细节,并不能变为真正可实践的系统,然后即使设计了分布式一致性系统,具体实现也已经Paxos没有了关系。