raft协议 论文

概述

    raft通过操作日志备份的方式实现一致性算法。在达到的效果和可用性上与paxo相比没有什么区别,但是思维方式却有很大的不同;raft比paxos更容易理解,也更容易在此基础上工程化。为了便于理解,raft将一致性算法分解成了几个关键部分,例如,leader选举、操作日志分发备份、算法安全等,另外,为了更高强度的一致性(弱一致性----->强一致性,状态越少,速度越快,一致性越能得到保证),raft尽量的减少需要考虑的状态空间(state space)。从一份儿用户调查结果来看,raft比paxos更容易让学生们理解。对于运行当中的服务器集群,服务器数量(数量发生变化,表明配置文件就需要发生变化,针对老配置文件失效,新配置文件生效的过程)发生变化等情况,raft采用转换过程中老集群、新集群中的分别多数服务器同时生效的方式(overlapping majorites),保证服务的安全准确和可服务性。

1. 介绍

    一致性算法,允许一组机器像一台服务器那样提供一致性服务,即使里面有少数几台服务器发生了崩溃,也不会影响整个服务器集群的可服务性。所以一致性算法在大型、可扩展的软件系统建设中,起着很重要的作用。paxos理论已经主导了一致性算法理论很多年:很多一致性算法的实现都是基于paxos理论或者受到它的影响,paxos理论已经成为了学生们学习一致性算法主要的途径。

    不幸的是,paxos原理很难被理解和掌握,尽管很多人尝试了很多的方法。更甚的是,如果我们想依赖paxos去进行具体实现,需要作出很多复杂的改变。因而,无论是软件系统开发人员,还是学习者,都对paxos怨声载道。

      我们自己对paxos的难理解性也是怨声载道,因此我们想自己找出一种新的一致性算法,这种算法既能容易被理解,也更容易工程化。我们目的是让它能够容易被理解:一种既能应用于真正的系统,与paxos相较起来也更容易被描述和理解的一致性算法。更是为了让系统构建者思路清晰顺畅的去构建系统。能让算法正常运行很重要,但是弄明白为什么能够正常工作更重要。

    这就是raft,一种新的一致性算法。在设计raft的过程中,我们采用了很多特别的技术来增强它的可理解性,包括分解(将raft分为leader选举、日志分发备份、安全),同时对状态空间(state space,可以百度一下)进行压缩(相较于paxos而言,raft大幅度的减少了未确定性及可能引起不同服务器间不一致的方法)。通过针对来自两所大学43名同学的调研,发现raft要比paxos容易理解的多:经过对两种算法的学习,其中的33名同学,能更好的回答关于raft算法的问题。

    raft在很多方面与已经存在的一致性算法有相似之处,但是它有几个显著的特性:

  • Strong leader:相较于其他一致性算法,raft将更多的工作交由leader来完成。例如,日志记录只从leader服务器流向其他的服务器。这样就简化了日志分发同步的管理流程,也使raft更容易理解。
  • Leader election:raft利用随机的时间进行leader选举。这种方式只是将所有一致性算法的心跳机制稍微做得复杂了一些,但是能够简单地、快速地解决选举过程产生的冲突。
  • Membership changes:针对集群中服务器的变化,由老配置切换为新配置的过程,raft采用一种叫做“joint  consensus“”的方式来保证安全,这种方式在新老配置切换的过程要求,基于老配置的服务器多数和基于新配置的服务器多数同时生效,才算生效。

    我们相信,与paxos和其他的一致性算法相比,在容易理解的程度和具体实现落地方面,raft更有优势。raft更简单,也更容易被理解;在具体怎样落地实现方面,也做了全面的描述;现在已经存在了几个开源的项目,也应用到了几家公司的实战当中;安全性、正确性也得到了说明和验证;效率方面和其他的算法相比也不落下风。

    在后续的章节中,我们会介绍状态机在分布式环境中如何进行复制操作(章节2),讨论一下paxos的长处和短处(章节3),大体描述一下raft的可理解性(章节4),对raft进行详细表述(章节5-8),预估一下raft近期和将来还需要做的相关工作(章节9-10)。

2. 备份状态机(Replicated state machines)

    一致性算法贯穿于备份状态机(Replicated state machines)的整个运行过程 。 有赖于一致性算法,状态机集群里的各个服务器才能按照完全相同的顺序计算出完全相同的状态,也因此,当集群中的少数服务器发生崩溃时,整个集群还能正常提供服务。备份状态机机制是分布式系统中可能出现的各种问题的容错机制。在一些大型可扩展的系统当中,例如GFS、HDFS和RAMCloud,都会利用备份状态机去管理集群leader的选举过程,且存储相关信息,以便在leader发生崩溃时,集群能够立马选出新的leader,从而不会影响到整个集群的可用性。备份状态机最典型的应用案例就是Chubby和Zookeeper。

    实现备份状态机最典型的方式就是通过备份操作日志(replicated log),如图1所示。集群中的每台服务器都包含一个日志记录数组,里面包含一系列命令,状态机会按照顺序执行这些命令,从而修改状态机的状态。因为集群中每台状态机服务器包含的日志都是一样的,并且日志里的命令、命令的顺序也都完全一样,所以各台状态机执行的命令都是完全一样的。由于所有状态机服务器执行的都是相同且确切的命令,因此,它们会产生相同的状态和输出结果。

图1:备份状态机架构图。一致性算法负责管理每台服务器上日志的备份,日志里面是从客户端传过来的命令集合。因为各台状态机都会执行相同日志里面的命令,且这些命令及命令的顺序也都是完全相同的,所以会产生相同的结果。

    保证备份日志(replicated log)的一致性是一致性算法的工作。一台服务器上负责一致性的模块接收客户端的命令,并将命令追加到自己日志的后面。然后和其他服务器上的一致性模块进行通信,将刚刚接收的命令同步到其他服务器上,并且保证其他大部分服务器也是以相同的顺序将命令各自追加到自己的日志里,可以有少部分的服务失败。当客户端发过来的命令请求已经备份到各台服务器上,且被执行后,执行结果会返给客户端。对客户端来讲,整个过程就像一台高可用性的状态机在提供服务,而不用关心集群内部的结构。

    在实际的系统当中,一致性算法要具有以下几个典型的特性:

  • 在没有拜占庭问题出现的环境中,一致性算法必须保证结果的安全、正确性(不能返回错误的结果),即使会出现网络延迟、断网、丢包、包重复发送、包无序等问题。
  • 只要大部分的服务器在正常工作,且彼此之间和与客户端之间能正常通信,就能够提供正常的服务。所以,一个典型的由五台服务器组成的集群中,能够忍受其中的两台发生问题,还能正常提供服务。假如服务器临时被关机而失去服务能力,后续可以通过已经持久化的配置文件重新恢复功能,并重新加入到集群当中。
  • 不会依赖于时间来保证日志的一致性。时钟错误、消息长时间延迟等情况,在最坏的情况下,也只是引起性能问题,而不能影响到一致性。
  • 在正常情况下,只要大部分服务器在一轮RPC调用中完成了计算并返回了结果,则一条命令就算执行成功了;小部分反应速度较慢的服务器不会影响到整个集群的性能。

3.paxos的主要问题是什么?

      在过去的十几年,paxos几乎已经成为了一致性算法的代名词:无论是作为教程还是作为实际系统实现的基础。paxos首先定义了single-decree paxos协议,它能够让集群对单一的决议达成共识,例如备份一份儿日志记录。在此基础上,paxos可以将多个这样的实体组合起来,进行一系列的决议,这种方式就是multi-paxos。paxos能够保证安全性和可用性,还支持集群关系变化。paxos理论的正确性已经得到了证明,但是效率一般。

    paxos存在两个非常严重的缺点。一个是paxos很难理解。paxos的整篇阐述(lamport-paxos)非常含糊,除非用尽大力气,不然很少有人能够搞懂它。因此,有很多人想以一种简单的方式对它进行阐述。虽然这些阐述主要集中在single-decree paxos上,但是在可理解性上还是面对着一些挑战。经过对一些参加NSDI 2012大会的参会者的调研,我们发现很少有人能够适应paxos,即使其中一些人是相关领域的研究人员。我们自己对paxos也是怨声载道;我们也是经过研究其他简单化的阐述文章后,才对paxos有了全面的了解,因此我们决定设计另外一套一致性算法以替代paxos,经过一年多的努力,我们完成了这个任务。

    我们猜测,paxo理论的模糊性是因为它主要是以single-decree作为基础进行构建而引起的。Single-decree Paxos的操作很密集且过于敏锐:它被分成了两个阶段,但是每个阶段都不能直观的进行阐述,因为只有两个阶段连接起来才能被理解。因为这个原因,就很难让人产生一个直观的认识:single-decree协议到底是如何工作的。至于multi-paxos的构建规则更加复杂和敏锐。但是我们相信通过一些方法进行分解后,multi-paxos为实现多决议(multiple decisions)(例如,整个日志进行备份,而不是一条日志记录)而面临的问题能够直观的、显而易见的被解决掉。

    第二问题是paxo没有为具体的实现提供很好的基础。一是因为没有被广泛认可的实现multi-paxos的算法。另外就是Lamport只描述了single-decree paxos,而对于multi-paxos的实现方式只是进行了一些延展,并没有进行详细的描述。现在有一些对paxos进行充实和优化的尝试,但是彼此之间及与Lamport的延展思维之间有很大的区别。有一些系统,例如Chubby已经实现了类paxos算法,但是很多具体的实现细节没有公布。

    另外就是,对搭建具体的系统来讲paxos框架显得很单薄。这是single-decree paxos按二阶段进行拆解的另外一个后果。例如,我们先选出不同的日志记录(log entries,阶段一),然后再将他们按顺序追加到日志文件里面(阶段二)感觉毫无意义,只是增加了复杂度。可以设计一个日志系统,新的日志记录可以按照顺序直接追加在日志文件后面(每个日志记录会按全局有序的方式被授予一个ID),这样更简单,效率也更高。另外一个问题就是,paxos的核心采用对称的点对点(symmetric peeer-to-peer,指每个peer都可以发起proposal?)的方式(虽然最终为了优化性能而采用了类leader选举的方案)。这种方式似乎只有在同时只需要一个决议的简单世界里才有意义,真实的系统肯定不能只实现这一种场景。如果同时需要进行一批决议,我们觉得,先选出一个leader,然后由这个leader去协同完成这些决议,会更简单更快速。

    综上所述,其结果就是实际的系统实现与paxos相比已经发生了很大变化。每个系统开始时都是以paxos为基础进行实现,但是很快就会发现paxos很难被实现,最终就是实现了一套完全不同的框架。由于paxos的难理解性,造成实现的过程就是一个错误论证和时间无意义消耗的过程。paxos的公式对于理论的验证无疑是正确的,但是对于具体系统的实现确鲜有意义,因为实现与理论验证大不相同。下面这段话来自于Chubby的实现者,很有意义:

在paxos算法描述与具体的系统实现之间是一条条巨大的鸿沟。。。最终的实现将是基于未被论证协议之上的。

      由于这些问题,我们觉得Paxos既不适合作为系统实现的基础,也不适合于授课。但是一致性算法对大型可扩展的分布式系统来讲又至关重要,所以我们决定设计另外一套算法来替代paxos,它就是raft。

4.  以可理解性为目标进行设计

    在设计raft的时候,我们有几个目标:它必须为实际系统的搭建提供完整、可行的的基础,这样能够极大的减少开发人员的设计成本;在任何条件下,都必须保证安全、准确性,在一般的操作场景中,需要保证可用性;正常的操作必须保证效率。但是我们最重要的目标,也是最大的挑战,就是可理解性。它必须让大部分的观众能够容易理解。另外,能够让开发者基于这个算法进行直观的开发,以便在实际系统搭建过程中进行必要的扩展。

    在raft的设计过程中,有很多的点,我们要考虑采用怎样的方式来实现它们,我们是基于可理解性进行选择的:阐述每个选择的时候是否困难(例如,状态空间多复杂?是否包含一些敏锐操作?),对于读者来讲,是否能够容易理解这种方式的含义?

    我们认识到,在做这类分析的时候有很高的个人主观性;因此我们采用了两种较常用的技术。一种就是著名的问题拆解技术:只要有机会,我们就将问题拆解为可单独解决的、解释的、可理解的分片。例如,我们将raft拆分成了leader选举、日志备份、安全保证、集群成员关系变化四部分。

   第二种就是通过减少需要考虑的状态来简化状态空间,使系统更清晰,尽可能减少系统的不确定性。raft绝不允许备份日志操作有漏洞存在,限制可能引起日志间不同步方法的使用。虽然在大部分情况下我们竭力去减少不确定性,但是在某些情况下不确定性又确实能够改进可理解性。特别是一些将随机数作为入参的方法,虽然因此产生了不确定性,但是经过对这些所有可能的输入都采用相同的处理方法,所产生的结果是状态空间被压缩了(意思就是无论你的输入是啥,我都会产生相同的结果)。我们利用这样的随机策略去简化raft的leader选举算法。

5. 一致性算法 Raft

    如章节2所述,raft是一种管理日志备份流程的算法。图2对算法进行了简要的概括,图3列出了算法的一些属性;图里面的元素将在后续章节中进行详细的讲解。

图2:Raft一致性算法概要(不包含成员关系变化和日志压缩)。服务器的行为结果可以总结为最上面图框中状态的变化,而且这些行为是可以被重复的独立的触发。章节号,例如“章节5.2”标注了具体在哪些章节进行描述。一份儿官方的说明【31】对算法进行了详细的描述。

图3:raft保证里面所有的属性在任何时候都是成立的。章节号标注了这些属性在哪些章节进行详细讨论。

    Raft为了实现一致性,首先会选出一个唯一的leader,然后将所有关于管理日志备份的权限都交给这个leader,由leader去统协日志的备份。leader从客户端接收所有的日志记录,然后将他们备份到所有服务器上,并且会告诉他们什么时候执行这些日志记录里的命令,以便改变状态机的状态。通过这种单leader机制,能够极大的简化日志备份的流程。例如,leader不用跟其他的服务器交流,而是自己决定新的日志记录在日志中的插入位置,而且日志数据只会从leader流向其他的服务器。如果leader发生了崩溃或者与其他的服务器不能进行通信了,就需要选举一个新的leader出来。

图4:服务器状态。追随者只会响应其他服务器的请求(leader和候选者)。如果追随者没有收到来来自leader或者候选者的通信信息(心跳请求或者选举请求),那么它就会成为候选者发起leaeder选举。如果大部分服务器投的是赞成票,那么它就会成为leader。且会一直负责工作直到自己发生崩溃。

图5:时间被分成不同的期次,每个期次都以leader选举开始。如果成功选举出一个leader,那么,在这个期次的后续过程中,成功选出的leader就会负责整个集群的运行。如果选举失败了,这个期次就会以无leader的形式结束。期次的转换可能会被不同的服务器在不同的时间点监测到。

    因为采用了单leader这种策略,raft将一致性算法分成了相对独立的三部分,在后续章节中,我们将逐一讲解:

  • Leader election:如果当前的leader失效,那么就必须选出一个新的leader(章节5.2)。
  • Log replication:leader负责接收从客户端传过来的命令请求,将他们封装为日志记录,然后备份到集群所有服务器上,并且强制其他的服务器以相同的顺序追加到日志后面。(章节 5.3)
  • Safety:raft的安全属性就如图3中所描述的状态机安全:如果有一台服务器已经执行了一个的特定日志记录所包含的命令,且改变了状态机状态,那么,对于其他的服务器来讲,就不能从相同下标的日志记录里执行到不同的命令。章节5.4描述了raft如何保证这个特性的;这个解决策略要求leader选举机制添加一些额外的限制,将在章节5.2中进行了描述。

    在对一致性算法进行了一些表述以后,在本章后续的小节中,我们将讨论一下可用性的问题和时间在系统中的用途。

5.1  raft基础

    一个raft集群包含多台服务器;5台是一个比较典型的配置,这种配置条件下,允许其中的两台发生崩溃。每台服务器在任何时间,都只会处于以下三种状态中的一种:leader,追随者(follower),候选者(candidate)。一般情况下,只有一台作为leader,其他的服务器都作为追随者。追随者处于被动状态:他们不会发起提议请求,而只是对leader或者候选者发起的请求进行应答。leader处理所有来自客户端的请求(如果一个客户端只链接到了追随者上,那么这个追随者会将客户端的请求转发给leader)。候选者用于选取leader,将在小节5.2进行描述。图4展示了这些状态及这些状态间的转化过程;接下来我们讲述状态的转换过程。

    如图5所示,raft将时间分成随意长度的期次(term)。期次用连续的整数进行标识。每一期都从选举开始,如在小节5.2所表述的,在选举的过程中,一个或者多个候选者会争取成为leader。如果某个候选者赢得了这次选举,那么在这一期的后续时间里,它将作为leader的角色。在某些情况下,可能会出现选举脑裂的问题,这样的话,这一期将以没有leader的方式结束,并很快启动一个新的期次(当然,是以leader选举开始)。rafte能够保证在任何的期次里都只会有一个leader。

   不同的服务器可能在不同的时间里观察到期次里的状态转变,甚至在某些条件下,一台服务可能都没察觉到一次选举过程或者整个期次。在raft里面期次是起逻辑时钟的作用,通过期次服务器可以检测到过时的信息,例如某leader已经失效。每台服务器都会存储当前期的期次号,随着时间的推移(期次的变化),期次号会线性的变大。不同的服务器之间在进行通信时,它们会针对当前期信息进行交流;如果某一台服务器发现自己当前期的期号比其他的服务器期号要小,它就会将自己的期号更新为最大的那个。如果是候选者或者leader自己发现自己的期号已经过期了,它们就会立马转换为追随者状态。如果一台服务器接收到了包含过期期号的命令请求,它将拒绝这个请求。

    服务器间的通讯采用RPC(remote procedure calls)方式。主要包括两类RPC请求,一种是选举请求(RequestVote,章节5.2详细描述),由候选者在竞选leader时发出,另外一个就是日志追加请求(AppendEntries,章节5.3详细描述),由leader向其他的服务器备份日志时发出。第7章还介绍了另外一种RPC调用,是在服务器之间同步日志快照的RPC调用。如果没有及时收到回复,服务器会不停的尝试发送RPC请求,为了保证性能,他们会同时向多台服务器发出请求。

5.2 leader选举

    raft通过心跳机制去触发leader选举。当服务器启动以后,它们默认处于追随者的状态,只要能够收到来自leader或者候选者的有效PRC调用,它们就一直处于追随者状态。leader会定期发送PRC请求(一种日志追加格式的RPC请求,只是要追加的日志为空)给其他的服务器,告诉他们leader现在还处于有效状态。如果追随者在一段时间(这段时间被称为需要重新进行选举的过期时间,election timeout)内一直没有收到来自leader的心跳请求,那么它们就会认为当前没有leader了,从而发起新一轮儿的leader选举。

    如果一个追随者想发起选举,自己成为leader,它会将当前期的序列号加一,并转变为候选者。然后,它会同时向集群里的其他服务器发起选举请求,告诉它们自己想成为leader。这个过程将会以以下三种结果的一种结束:(a)它赢得了这次选举,成为了leader,(b)其它的一台服务成为了leader,(c)一段时间过后,没有任务服务器胜利的成为了leader。

    在同一期次的选举过程中,如果某个候选者获得了集群中大部分服务器的投票,那么它就赢得了这次选举,成为leader。在某个特定的期次内,按照先来先获得投票的方式,一台服务器将票投给一个候选者且只能投给一个候选者(注意:章节5.4对选举增加了一些额外的限制)。在某个特定期次内,只有获得多数服务器的投票才能成为leader这个规则,保证了在一个期次内,只会有一个leader存在(如图3所示的选举的安全属性)。当一个候选者赢得选举成为leader后,它会立马向其他的服务器发出心跳请求,告诉它们自己已经成为了leader,不要再对其他的选举过程进行投票了。

    在候选者等候投票的过程中,如果收到了来自其它候选者的心跳请求(Appendencies RPC),且传送过来的期次号不小于自己的期次号,那么这个候选者就会认识到leader已经产生了,自己就会回归到追随者的状态。如果收到的心跳请求包含的期次号,小于自己持有的期次号,那么就会拒绝这次请求,继续等待其他服务器的投票。

    第三种情况就是,一个候选者既没有赢得选举,但还是在等待投票:多个追随者同时成为了候选者并发起了投票,那么这个时候整个集群就有可能被划分成了很多小块儿(脑裂问题),没有一个候选者能够赢得投票。如果出现了这种情况,那么经过一段过期时间后,候选者就会放弃这次投票,然后将期次号+1,发起新一轮儿的选举投票。但是,如果没有其他的限制策略,就会进入一个死循环的状态。

    raft采用随机的过期时间来确保选举脑裂问题(split votes)很少发生,即使发生了也会很快的得到解决。为了避免集群刚开始的时候出现选举脑裂(split votes)问题(集群里的服务器都是刚启动,有可能会同时去竞选leader),会为每台服务器设定一个一定范围内的随机过期时间(例如150ms~300ms内的一个时间段),只有过了这段时间,服务器才能去竞选leader。这样的话就把服务器参与竞选的时间点分散开了,在大部分情况下,某个时间点可能只有一台服务器发起投票;从而保证了成为候选者的服务器有可能在其他的服务器发起投票前就赢得选举,并向它们发出心跳请求,告诉它们leader已经产生了,不再需要发起投票了。随机过期时间策略,也会被用在后续的选举过程中来防止选举脑裂问题:每个候选者在每次发起选举前,都会随机的设置它的选举过期时间,当过了这段时间以后,如果还没有产生leader,那么它就会发起投票;因为随机的原因,这样就有效防止了在新一轮选举中发生脑裂的可能性。章节9.3还描述了这是一个快速的leader选举策略。

    选举算法这个例子很好的解释了我们是如果从理解性的角度上来做选择的。刚开始的时候我们打算采用等级策略进行选举:每个候选者都被分成不同的等级,当一个候选者发现有其它的候选者等级比自己高时,就会放弃选举,变为追随者模式,以便具有高等级的候选者更容易的赢得下这次选举。但是我们发现在可用性上会存在一些细小的问题(一个处于低等级的服务器在高等级的候选者崩溃以后,会成为候选者、甚至是leader,但是要给它设定一个过期时间,因为高等级的服务器随时可能会恢复服务,如果经常发生这种情况,就会很频繁的进行leader选举)。我们对这个算法进行过几次调整优化,但是每次都会产生新的问题。最终我们得出结论,基于随机过期时间的选举策略更具优势,也更容易被理解。

5.3 日志备份

    当一个leader被选出以后,就开始接收来自客户端的请求。每个客户端的的请求都会包含一条命令,所都的状态机都将会执行这条命令。leader首先将这条命令封装到一个日志记录里面,然后将这条日志记录追加的日志队列后面,接着就会将这条日志记录以AppendEntries调用的形式同时传送给其它服务器去进行备份。当日志记录在其它大部分服务器上成功且正确(怎么算成功、正确的完成了备份,将在下面进行讨论)的备份(已提交状态)以后,leader就会执行这条命令(改变状态机状态,追随者有可能还没有执行?)并将结果返回给客户端。如果有些追随者发生了崩溃或者运行很慢,亦或者发生了网络丢包问题,leader会不停的发送AppendEntries请求(即使leader已经将请求结果返给了客户端)直到所有的追随者都将所有的日志记录备份成功。

    日志的结构如图6所示。当leader接收到一个请求时,都会创建一条日志记录,日志记录里会包含一条会被状态机执行的命令和这条日志记录产生时所属期次的期次编号。日志记录的期次号用来帮助检测不同的日志之间是否有不同步的情况,同时会被用来保证图3中所示的一些属性。每条日志记录还包含一个整数形式的下标,用来标明自己在日志数组中的位置。

图6:所有的日志都由多条按顺序排列的日志记录组成。每条日志记录都会包含它被创建时所在期次的期次号(方格里正上方的数字代表期次号)和一条会被状态机执行的命令。如果我们觉得一条日志记录里的命令可以被状态机安全正确的执行了,那么这个时候我们就认为这条日志记录被提交了。

    一条日志记录里的命令什么时候可以被所有状态机安全正确的执行(有可能大部分完成了,就算全部完成了)由leader决定;这个时候的日志记录就处于已提交状态了(committed)。Raft会保证所有已提交的日志记录都已经持久化,且里面的命令会被所有处于运行状态的状态机执行。当一条日志记录被leader创建后被大部分服务器都成功备份了,那么这条日志记录的状态就会变成提交状态(如图6中的下标为7的日志记就已经处于提交状态)。当然,leader日志里所有在这条日志记录之前的记录都会被提交,包括前面期次leader产生的日志记录(只要这条日志记录已经被大部分的服务器成功备份了)。我们将会在章节5.4中详细讨论leader发生变更的情况下,日志记录提交的一些细节,那时候会证明日志记录提交策略是安全没有漏洞的。leader在任何时候都知道下一条即将被提交(不是要备份的)日志记录的下标是什么,这个下标将会被包含在下一条AppendEntries请求里(下一条也有可能是心跳请求,如前所示,心跳请求本身就是AppendEntries请求,只不过里面的内容为空),所有服务器(追随者)就会得知哪些日志记录需要被提交了。追随者就会将相应的日志记录进行提交,并在本地执行日志记录里面包含的状态机命令(当然是按照日志里日志记录的顺序来执行)。

    我们设计的日志备份机制能够确保各台服务器里的日志具有强一致性。这种策略不仅仅简化了系统操作、增强了可预见性,更为重要的是它保证了安全性(正确、无漏洞)。下面列出的特性,共同构成了图3中所标示的日志匹配(Log Matching Property)特性:

  • 如果在不同日志里的两条日志记录拥有相同的下标和期次号,那么它们包含的命令肯定是一样的。
  • 如果两条日志记录在不同的日志里拥有相同的下标和期次号,那么这些日志在这个下标以前的日志记录都是完全相同的。

    从以下的事实中很容易的就能得出第一条特性:一个leader在一个下标里只能产生一条日志记录,且日志记录在日志(数组)里的位置任何时候都不能发生变化。第二条特性则通过追加日志时的一致性校验来完成。当发送AppendEntries请求时,leader会把这次新的需要同步的日志记录的前一条记录的下标和所在期次同时发出去。如果追随者在它的日志里没有发现相同下标、相同期次的日志记录,它将拒绝这次AppendEntries请求。这个一致性校验是以下归纳法中的一步:初始状态时,因为各个日志里的内容都为空,所以符合了日志匹配(Log Matching Property)的特性,而后续在进行日志追加的时候,一致性校验同样保证了这个特性。结果就是,只要追随者在接收到AppendEntries调用后返回的是成功,那么leader就能确认追随者当前的日志与自己当前的日志是完全一样的。

    在正常的运行环境中,leader的日志与追随者的日志是会一直保持一致的,所以追加日志时的一致性校验很少会失败。但是,如果当一个leader发生了崩溃,就有可能会发生不一致的问题(例如leader在崩溃之前,接收了很多请求,但是还没来得及完全确认这些日志记录,就发生了系统崩溃)。这些不一致性会随着几次leader或者追随者的崩溃而叠加在一起。图7展示了一个追随者的日志如何与当前leader的日志变得不一致的过程。一个追随者即有可能缺失了一些在leader里的日志记录,也可能会比leader中的多出一些,或者是既有缺失的也有多出的。缺失的或者多出的日志记录可能经过几次期次更替叠加在一起引起的。

图7:如果当一个leader生效以后,它的日志结构如最上面一行所示,那么下面的(a~f)的场景都有可能发生在追随者的日志里面。每个方格代表一条日志记录;方格里的数字代表日志记录所处的期次号。一个追随者的日志里面可能会有日志记录缺失(场景a~b),也可能会包含还没有提交的日志记录(场景c~d),或者两者同时存在(场景e~f)。例如,场景f有可能是这样发生的:如果一台服务器在期次2的时候成为了leader,并追加了一些日志记录,但是这些日志记录确认提交以前,这台服务器就崩溃了;很快,这台服务器又恢复了且又成为了期次3的leader,又追加了一些新的日志记录,但是连同期次2里面的这些日志记录在提交前,服务器又崩溃了,并且宕机了好几期(次)。

    在raft里面,leader会强迫所有的日志不一致的追随者重写成自己的日志。这就意味着追随者需要重写与leader冲突的日志记录,将其改为leader的日志记录。章节5.4会向我们描述,结合一些其它的限制,这么做是安全的(不会有漏洞或者会给客户端返回错误的结果)。

    为了能够让追随者的日志与自己的日志保持一致,leader必须找到自己与追随者最近的还保持一致的日志记录的下标是多少,然后将追随者日志里这个下标以后的日志记录全部删除掉,替换成自己的。所有这些操作都是在进行AppendEntries调用时进行的一致性校验的过程中完成的。leader会为每个追随者维护一个nextIndex,通过这个坐标值leader就会知道将要向追随者同步的下一条日志记录。当一个leader生效后,它会将每个追随者的nextIndex默认设置为它最后的日志记录的下标再+1(如图7中的11)。如果追随者的日志与leader的日志不一致,那么在进行下次AppendEntries请求操作时,一致性校验就会失败,此时,leader就会将nextIndex减1,然后,继续发送AppendEntries请求,重复此过程,直到leader与追随者找到匹配日志记录的下标值。接着追随者就会删除这个下标以后的所有日志记录,再追加leader在相同坐标后的日志记录(如果存在)。如果追加日志成功,追随者的日志就与leader的日志一致了,在期次的后续时间里,将会不断的重复这些操作。

    如果有意愿,我们可以改进这套协议以便减少可能发生的AppendEntries请求。例如,我们可以不用一条日志记录就发送一个RPC请求来确认哪一条是匹配的,当追随者在接收到AppendEntries请求后,可以将本期次内冲突的日志记录和本期内第一条日志记录的下标都返给leader。得到这些信息后,leader就会将一期作为冲突的单位,从而跳过不必要的冲突检测。在实际场景中,我们怀疑这些优化是不是必须的,因为很少会有系统崩溃问题,也就很少有日志不一致问题。

    通过这种机制,当leader开始生效的时候,不需要任何额外的操作来保持日志的一致性,它只需要执行正常的AppendEntries操作,利用里面的一致性检测机制就能让整个集群的日志达成一致。而一个leader从来不会重写或者删除自己的日志记录(图3中Leader Append-Only特性需要保证的)。

    这种日志备份的机制很好的满足了我们在章节2中描述的所需要的一致性特性:只要集群的大部分服务器都运行正常,leader就能够接收、备份、执行新的日志记录请求;一般情况下,我们只需要同大部分的服务器进行一轮RPC通信,就能完成日志的备份,至于少部分的响应较慢的服务器并不会影响整个集群的性能表现。

5.4 安全性(safey)

    前面的章节详细介绍了Raft如何进行leader选举、如何进行日志备份。但是这些机制从安全性(safty)的角度来讲是不够充分的:所有的状态机是否准确的按照相同顺序执行了相同的命令。例如:在一个leader提交日记记录的同时,一个追随者可能处于宕机的状态,然后这个追随者苏醒了,并被选举成为了leader,那么它就有可能重写前leader的日志记录,从而造成状态机执行了不同顺序的不同命令。

    本章节将会对一台服务器能够成为leader再加一些限制。这些限制能够保证一台服务器在成为leader的时候,无论它的日志处于哪个期次的状态,它都会将前面期次中已经提交的日志记录按顺序包含进来(图3中Leader的完整性特性-The Leader Completeness Property)。有了这些限制,日志提交的规则将会变得更精确。最后,我们将会对leader的完整性(Leader Completeness Property)进行一次论证,以证明这个特性是能够保证状态机正确地进行日志备份的。

5.4.1 选举的限制条件

    在所有的leader作为基础的一致性算法当中,leader都必须保证最终存储了所有已提交的日志记录。例如:Viewstamped Replication[22]算法,一个leader在没有包含所有已提交日志记录的情况下也可以被选为leader。但是算法会提供另外的机制,保证在选举过程中或者成功后不久,将丢失的日志记录传送给leader。不幸的是,这会给算法增加额外的机制和复杂性。而Raft会采用一种比较简单的方法:保证所有候选者在参加选举之前就包含了所有已经提交的日志记录,而不是在候选者成为leader后,再将缺失的日志记录传送给他。这就意味着日志的流向只会从leader到追随者,而且leader从来不会重写它的日志记录。

    在选举过程中,如果一个候选者没有包含所有已提交的日志记录,那么Raft就会阻止它被选举为leader。为了能够选举成功,在选举的过程中,候选者需要联系集群里大部分的服务器,这也就意味着完整提交的日志肯定会出现在最少一台服务器上(前面描述了,日志记录成功提交的前提,是大部分的服务器完成了备份)。如果候选者的已提交日志记录与大部分服务器里的已提交日志记录相比都是最新的(up-to-date,何为update-to-date将会在后面进行详细描述),就表明它拥有完整的已提交日志记录。Raft将这些操作包含在了RequestVote调用中:候选者发起的RequestVote调用中会包含自己的日志信息,接收到请求的投票人(候选者或者追随者)会先拿候选者的日志与自己的进行比对,如果自己的日志要比候选者的日志新,则会拒绝这次请求。(题外:思考一下,感觉还是有问题,候选者会跟大部分的服务器通信,也就是n/2+1,如果碰巧只有一台的日志比自己新呢?)

    raft是这样定义两个日志中的哪个日志更新的(up-to-date):分别拿出两组日志中的最新提交的日志记录,然后比较这两条日志记录的下标和所在期次号,如果其中一条记录的期次号比较大,那么它就是较新的,如果期次号相同,再比较下标值,下标值较大的(下标值较大,也就说明日志(日志是数组形式)较长)则是较新的那一条。

5.4.2 提交以前期次中的日记记录

    如在章节5.3所描述的,如果一个leader知道本期的一条日志记录在大部分的服务器上都成功保存了,那么这条日志记录就算提交了(感觉这个规则太主观了)。如果在提交之前leader崩溃了(只有leader能够确认日志记录是否被提交了),那么后续的leader会想办法去完成这些日志记录的提交动作。但是,当前的leader并不能立刻就判断出来前面期次的日志记录是否需要被提交,即使这些日志记录已经被大部分服务器所备份。图8描述了这种情况,虽然一条日志记录已经被大部分的服务器备份,但还是有可能被未来的leader覆盖掉。

图8:以时序图的方式展示了一个leader为什么不能立马决定前一期的日志记录是否需要被提交。在阶段(a),S1作为leader,部分的备份了下标为2的日志记录,在阶段(b)的时候S1发生了崩溃;S5在期次3通过获得S3、S4的和自己的投票,成为了leader,且在下标2的位置上接收到了一条不一样的日志记录,然后发生了崩溃;S1重启,又一次被选为了期次4的leader(投票可能来自S2、S3、S4和其本身,参照选举规则,S1是可以被选为leader的),它会继续备份期次2里的日志记录,且在大部分的服务器上完成了日志备份,但是日志记录没有被提交(例如,服务器接收到备份命令,且完成了备份,在返回结果的时候,结果因为网络原因发生了丢失)。S1又发生了崩溃,在阶段(d),S5又一次被选为了leader(期次5,来自S2、S3、S4的投票),此时就有可能发生所有服务器下标为2的日志记录被S5的日记记录重写的情况。但是,如果S1在发生崩溃之前,在期次4的时候备份了本期次内的一些日记记录到大部分的服务器上了,如阶段(e)所示,那么期次2里的日志记录就会被提交了(因为此时S5不可能赢得选举了,参照前文的选举规则)。

    为了避免图8所示情况的发生,raft不会让当前期次的leader对前面期次里的日志记录进行计数(得到是否已经被大部分服务器进行了备份)从而主动发起提交动作。Raft只允许leader对当期的日志记录进行提交,只要leader本期次的日志记录进行了提交动作,那么根据日志匹配的属性要求(Log Matching Properties),前面的日志记录也就被变相提交了(思考:如果当前期次一直没有提交动作,是否会有问题产生,即使保证的是弱一致性?)。在某些条件下,虽然Raft可以判断出一条日志记录是否经被提交了(例如,一条日志记录已经被所有的服务器都进行了备份),但是为了简单,raft采用了比较保守的方式(直接覆盖)。

    由于Raft在备份以前期次里的日志记录的时候,需要保留日志记录生成时的期次号与下标位置不变,导致了Raft在进行日志记录提交的时候有一些复杂。在其他的一致性算法中,如果一个leader需要对以前期次产生的日志记录进行备份,那么它需要对这些日志记录按照当前期重新进行编号。由于Raft会保留一条日志记录生成时的期次号与下标,这就会让我们在对日志记录进行分析的时候变得容易一些。另外,Raft在产生一个新的leader后,和其他的算法相比,这个leader很少会发送有关以前期次中日志记录的信息(其他的算法在对这些日志记录进行提交的时候,需要对这些日志记录进行重新编号,然后同步给其他的服务器,Raft的规则则不需要这么做)。

5.4.3 安全性论证(Safety argument)

    完成对Raft算法的全部叙述以后,我们现在可以对raft的leader完整性(Leader Completeness Properties)进行更精确的论证了。我们先假设leader的完整性是不成立的,然后通过反例来证明这样是错误的(反证法?)。假设期次T的leader(LeaderT)在期次T内提交了一条日志记录,但是这条记录在未来某个期次leader的日志里面不存在。让我们来论证一个最小的期次U(U>T),它的leader(LeaderU)没有保存这条日志记录的情况。

1.这条已经被提交的日志记录在LeaderU被选为leader的时候,肯定没有出现在LeaderU的日志里面(因为leader从来不会删除或者覆盖自己的日志记录)。

2.日志记录被确认提交的时候,这条日志记录肯定已经被集群中大部分的服务器所备份,同时,LeaderU能够成为leader,肯定也获取了来自大部分服务器的投票。这就说明,最少有一台服务器即备份了来自LeaderT的日志记录,也给LeaderU的选举投了赞成票,就如图9所示。这台即备份了日志记录,又参与了投票的服务器是形成反例的关键点。

图9:如果S1(期次T的leader)在它的期次内提交了一条日志记录,在随后的期次U中,S5被选举为了leader,那么最少有一台服务器(本图中为S3)即备份了LeaderT提交的日志记录,也给LeaderU的选举投了赞成票。

3.这台参与投票的服务器(Voter)肯定在投票之前就备份完成了来自LeaderT的日志记录;否则,如果AppendEntries请求是在选举投票之后,那么这条AppendEntries请求就会被拒绝掉(因为,这条请求里包含的期次号要小于这台服务器当前的期次号)。

4.直到参与到LeaderU的投票选举时,这台服务器(Voter)都会保存这条日志记录,因为在期次T与期次U之间的产生的leader们不会删除自己的日志记录,而追随者们只会删除与leader产生冲突的日志记录,所以这些leader们会存有这条日志记录(假设)。

5.当这台服务器为LeaderU的选举投赞成票时,说明LeaderU里的日志记录起码与这台服务器的相比肯定不会有缺失(up-to-date)。这就造成了两个矛盾点。

6.一个是,如果这台服务器最近一条日志记录的期次号与LeaderU最近一条日志记录的期次号是相同的,那么LeaderU的日志记录和这台服务器的相比,肯定是不会有缺失的,所以这台服务器包含的日志记录在LeaderU的日志里面肯定会存在。这就产生了矛盾,因为前面假设的是这台服务器里的已提交的日志记录在LeaderU里面是不存在的。

7.另外一个是,如果LeaderU最近一条日志记录的期次大于这台服务器最后一条日志记录的期次。或者说大于期次T,因为这台服务器最近一条日志记录的期次号不会小于T(因为它包含来自期次T提交的日志记录)。生成LeaderU最近一条日志记录的leader肯定包含这条已经提交的日志记录(假设)。而根据日志匹配规则(Log Matching Property),LeaderU的日志里面肯定也会包含这条日志记录,这又是一个冲突点。

9.经过反证。我们可以确定,后续期次号大于T的leader,它们的日志里面肯定会包含期次T内所有已提交的日志记录。

10.而日志匹配属性(Log Matching Properties)也保证了未来的leader会包含哪些通过间接方式提交的日志记录,就想图8(d)中下标为2的日志记录。

    验证了leader的完整性属性(Leader Completeness Property),我们就可以对图3中的State Machine Safety Property进行证明了,它的要求是这样的:如果一台服务器已经将某个下标的日志记录里的命令进行了执行,并且改变了状态机的状态,那么对于其他的服务器来讲,它们在相同下标的日志记录里的命令肯定是一样的。在某个时间,如果一台服务器执行了某条日志记录的命令,那么这条日志记录以前的所有日志记录与leader里相同下标的日志记录是完全相同的,并且,这条日志记录肯定是已经提交了的。现在让我们考虑这种情况:某台服务器执行了最早的一条需要执行的日志记录里的命令;那么Log Completeness Property保证了后续的leader们存储的是相同的日志记录,也就保证了后续的服务器们执行的是相同的命令。因此,也就保证了State Machine Safety Property。

    最后,Raft要求服务器执行的日志记录里的命令必须是按照相同顺序的。结合State Machine Safety Property,可以确保所有服务器在执行日志记录里命令时,命令都是相同的,且按照相同的顺序。

5.5  追随者和候选者发生崩溃的处理

    到现在为止,我们主要将精力集中在了如何处理leader崩溃的情况。追随者与候选者发生崩溃的情况处理起来就比较简单了,而且针对它们的处理方式是一样的。如果某台追随者或者候选者发生了崩溃,那么发给它的RequestVote和AppendEntries请求就会失败。如果请求失败了,Raft会向它不停的发送相同的请求,直到它重新启动。如果一台服务器在接收到请求后,但是在返回结果前发生了崩溃,那么它会接收到相同的RPC请求,由于Raft是保证幂等性的,所以不会造成危害。例如,如果一台服务器接收到了一条AppendEntries请求,但是里面的日志记录在它的日志里面已经存在了,那么它就会忽略这条请求,以此保证幂等性。

5.5  计时方式与可用性的关系

    我们对Raft的一个要求就是安全性不能依赖于时间:不能因为某个事件的发生比预想的快了或慢了就产生错误的结果。但是,系统的可用性(系统能够及时的将执行结果反馈给客户端)又不可避免的依赖时间。例如,如果通信过程中信息的交换时间比不同服务器间的崩溃间隔时间还长,那么,候选者就没有足够的时间去赢得选举(因为通信时间太长,候选者还没来得及接收投票结果,可能选举时间已经过期了,或者候选者已经发生了系统崩溃);相同的,也不会有一个稳定的leader(不能及时接收到心跳请求,其他的服务器就是发起投票),那么Raft就不能进行业务处理操作。

   时间对raft的重要性来说,leader选举只体现了一个方面。只要Raft依赖的底层系统能够满足以下公式的时间要求,那么Raft就能够成功进行选举,并维持一个稳定的leader:

在这个不等式里面,broadcastTime代表的是集群里的一台服务器同时给其他的服务器发送请求,并接收到结果的平均时间(round-trip time);electionTimeout如章节5.2里所描述的(如果某台服务器在这个时间内没接收到有效请求,将会发起投票,选举leader);MTBF是一台服务器发生崩溃的平均间隔时间。broadcast time应该远远小于electionTimeout,因为只有这样,服务器才能正确的接收到来自leader的心跳请求,从而保证leader能够正常运作。如前所述,election timeout是随机选择的,那么根据这个公式,选举过程中可能产生脑裂问题的概率就很小了。election timeout 也应该比MTBF要小很多,这样才能保证提供稳定的业务服务。当leader发生崩溃时,服务的不可用时间大概跟election timeout 差不多,这点儿时间从集群提供长时间的服务来看,是微不足道的。

    broadcast time 和MTBF的长短取决于依赖的底层系统,而对于election timeout的长短来说,我们可以进行相应的选择。Raft的RPC请求一般都要求接收方持久化一些信息,所以broadcast time大概在0.5ms到20ms之间,具体视底层系统而定。因此,我们可以将election timeout大概设定为10ms到500ms之间。MTBF时间典型的为几个月或更长时间,很容易满足我们的要求。

6 集群内服务器成员配置变化

    截止到现在,讨论都是基于集群的配置(例如参与一致性算法的服务器台数的变化)不会发生变化基础上的。但是在实际场景中,对集群的偶尔配置是必要的,例如,将失效的服务器替代掉、对日志备份强度要求的改变。虽然我们可以采取关停整个集群,然后进行配置,然后再重新启动整个集群的方式,但是这样就会造成集群在一段时间内是不可用的,另外,如果在配置的过程中,采用手工方式进行操作,还会增加失败的风险。为了避免这些问题的发生,我们决定采用自动化配置的方式,并将这种方式融合到raft的一致性算法里面。

    为了在配置文件改变的过程当中,保证系统的安全准确性,我们需要避免在转换过程中的某一时刻在同一期次内选举出两个leader。但是,无论采用什么方式,只要是从老配置切换到新配置,就出现某个时间点,系统不能保证安全准确性。因为,让所有服务器的配置同时从老的切换为新的是不现实的,就会导致在切换的过程当中集群被分裂为两个完全独立的多数(基于老配置的多数和基于新配置的多数),如图10所示。

图10:直接从老配置切换成新配置对于集群来讲是不能保证安全准确的,因为不同的服务器进行切换的时候,时间是不一样的。如图所示的例子,集群的服务器台数由3台增加到了5台,那么就会存在某一时刻,在同一其次内,选举出两个leader,一个是基于旧配置文件的(Cold),一个是基于新配置文件的(Cnew)。

    为了保证安全准确性,配置的切换必须采用两阶段的方式(two-case phase)。现在实现二阶段可以有很多的方法。例如,有些系统在阶段一的时候停止所有老配置(当然,这个时候服务是不可用的,响应到了客户端的请求),然后第二阶段启用新的配置。在Raft里面会采用一种临时配置的方式,我们管它叫:joint consensus;当joint consensus完成提交的时候,新的配置才会生效。joint consensus将老配置跟新配置组合在一起使用:

  • 日志记录需要在新老配置的服务器里都进行备份。
  • 来自两份儿配置里的任何服务器都有可能成为leader(如果是新增的服务器,应该不可能吧?)。
  • 协议的达成(表示选举投票和日志记录备份提交)需要同时拥有来自老配置中的多数与新配置中的多数的同意。

    采用joint consensus可以让每台服务器自己在不同的时间自己去做配置切换,而不用考虑整个系统的安全准确性问题。甚至,joint consensus能够做到在配置切换过程中,还能正确的响应来自客户端的请求。

    集群的配置被存储在特殊的日志记录(跟前面提到的日志记录是一个概念)里面进行传送;图11详细描述了配置的切换过程。当leader收到一个要将配置由Cold切换到Cnew的请求的时候,它会先生成一份儿配置,这个配置即包括Cold的配置,也包括Cnew的配置,也就是前面所说的joint consensus(图11中的Cold,new),然后作为一条日志记录追加到自己的日志后面,再然后按照前面介绍的方式同步备份给其他的服务器。如果一台服务器已经将新的配置内容的日志记录追加到了自己的日志里面,那么,在后续的操作过程中,它们都会依赖此配置作出决策(无论这条日志记录是否已经提交了)。这也就意味着,leader将会根据Cold,new的规则来决定基于Cold,new配置内容的日志记录什么时候算是提交了。如果leader发生了崩溃,那么新的leader的选出要么是按照Cold的规则,要么是按照Cold,new的规则,这取决于赢得选举的候选人是否收到了Cold,new这条配置内容相关的日志记录。不过,在这个阶段,肯定不会出现只基于Cnew配置的相关决策(Cold,new都还没提交)。

图11:配置切换的时间抽。虚线代表内容为配置信息的日志记录已经产生了,但是还没有提交,实线代表最新提交的内容为配置信息的日志记录。leader首先生成一条关于Cold,new的配置内容的日志记录,然后同步给其他的服务器(基于Cold配置的多数服务器和基于Cnew配置的多数服务器)并进行提交。然后再生成一条关于Cnew的配置内容的日志记录,同步、提交给基于Cnew配置的多数服务器。在这个过程当中,不会出现单独基于Cold或Cnew进行决策的情况。

    当基于Cold,new配置的日志记录被提交以后,基于Cold或者Cnew的配置就都不会单独做出决策了,而Leader Completeness Property保证了,只有现在的配置为Cold,new的服务器才会选举成功为leader。随后,就可以生成基于Cnew配置的日志记录了,然后将它同步备份到所有服务器上。同样的,只要这个配置信息被服务器备份以后,其中的配置信息就直接可以被采用了,而不用等到这条日志记录进行提交以后。当基于Cnew配置的日志记录被提交以后,旧的配置就失效了,那些在新配置中不存在的服务器也就可以下线了。就如图11所示的,在配置转化的过程中,基于Cold的配置或者基于Cnew的配置是没有时机做出单边决策的;这就保证了安全准确性。

    但是这种机制当中,会产生三个问题需要被注意和解决掉。第一个问题就是,新加进来的服务器,它的日志在初始化状态下可能是空的。如果它们以这种状态加入到集群当中,就是需要一段时间去同步日志来追赶上其他的服务器,而在这段时间内,就会造成新的日志记录不能被提交。为了避免产生这样时间间隙,而造成服务的不可用,Raft在配置切换之前增加了一个阶段,在这个阶段里面,新加入的服务器不能参与选举(也就是说,leader在给它们同步缺失的日志的时候,它们不能被当做多数服务器中的一员)。只能当这些新加入的服务器的日志已经追赶上其他服务器的日志的时候,才会开始前面所描述的配置切换过程。

    第二个问题就是,集群的leader本身可能在新的Cnew配置里不存在。在这种情况下,只要基于Cnew配置内容的日志记录一被提交,那么这个leader就会退回为跟随者的状态。这也就意味着会有一小段时间(基于Cnew配置内容的日志记录提交的过程中)leader管理着一个将要不包含自己的集群,它会向其他的服务器备份日志,但是自己不能算作多数服务器的一员。而leader的转换就会发生在基于Cnew配置内容的日志记录提交以后,这个时候是整个集群可以根据新配置Cnew做出决策的第一时间点(这个时候也才有可能从基于新Cnew配置的集群中选出leader)。在这个时间点之前,只有基于老配置Cold的集群中的服务器才有可能被选为leader。

    第三个问题是,已经被移除的服务器(在新的配置中被下掉的服务器)可能会扰乱集群。因为已经被移除了,所以它们将不会再收到心跳请求,当election timeout到了以后,它们就会发起选举RPC请求,由于发起请求时的期次号有可能大于当前的期次号,将会导致现在的leader退回为追随者的状态。虽然这样的选举不会成功(不能获取多数投票)新的合法的leader也会生成,但是已经被移除的服务器由于接收不到心跳请求,就会不停的发起选举投票,这样就会造成服务的可用性降低。

    为了防止这个问题的发生,Raft的服务器会判断当前是否有leader存在,如果它们觉得已经有leader存在了,就不会理会选举RPC,而是直接抛弃掉。具体来说,当一台服务器从上一次接收到来自leader的请求时开始计时,如果在最短的election timeout(election timeout虽然是随机时间,但是却是在一个上下沿内的随机时间,这里应该指时间下沿)时间内接收到了投票RPC请求,那么它们就可以将这条请求抛弃掉,而不会更新自己的期次信息,也不会对这次选举投赞成票。这样既不会影响正常的选举行为,因为所有的服务器都会在等待至少最短的election timeout的时间,还没接收到心跳请求时,才会发起投票,同时,也可以经过这段时间后,开始接收投票请求;还解决了来自移除服务器的干扰问题,因为如果一台leader能够保证它的心跳请求能够被所有服务器接收,就不会被拥有更大期次号的候选者发起的选举所取代。

7 日志压缩

    随着持续接收来自客户端的请求,Raft的日志会不断增长,但是,在实际当中,日志不能无限增长。因为随着日志的增长,将会占用大量的存储空间;出来了问题,可能还需要回放很长的日志,这将会需要很长时间。如果我们不能及时的将那些已经无用的堆积了很久的日志清除掉,最终就会影响到服务的可用性。

    快照(Snapshotting)是一种很简单的压缩方式。采用快照的的方式,状态机当前的整个状态信息(当前状态是执行日记记录里命令的结果)作为一个快照进行持久化,快照生成以后,截止到这个快照以前的所有日志将会被抛弃掉。Chubby和ZooKeeper都采用了快照的方式压缩日志,本章的后续内容将对raft的快照机制进行详细的介绍。

    增量压缩(Incremental approaches to compaction),如Log cleaning【36】所述的和log-structure merge trees,如【30,5】所述,都是可行性的压缩方案。这两种方案都是采用一次只压缩一部分的数据的方式,这样就可以将压缩操作分散开来进行了。他们首先会选择一块儿数据块儿,这块儿数据需要堆积了很多已经被删除或覆盖了的数据,然后将有效数据提取出来进行压缩,把其它的垃圾数据丢弃掉,从而释放这块儿数据块。这两种方案跟快照的方式相比,需要更多的保障机制,也就变得更复杂了,快照的方式只需要将状态机当前的整个状态进行一次持久化就可以了。如果在Raft里面采用log cleaning的方式,可能就要对Raft本身的一致性算法做出改变了,而采用LSM的方式倒是跟采用快照的方式采用一套接口(因为raft本身采用的是快照方式,如果将快照方式切换为LSM的方式,那么是可以基于一套接口的,换言之,对raft没有什么影响)。

    图12展示了Raft采用的快照方式的基本思路。每台服务器都会单独去生成快照,快照的内容覆盖到刚刚提交日志记录的内容(应该是日志记录提交后,里面的命令被执行后,状态机当前的状态内容)。生成快照的过程中,状态机要做的大部分工作就是将当前的状态写入到快照中。Raft会为快照维护少量的元数据:一个是last included index,代表生成新快照时,快照内容覆盖的最后一条日志记录(其实就是包含的命令已经被执行的最后一条日志记录),另一个就是last include term,代表最后一条日志记录所在的期次号。为了不影响集群成员关系变化的功能(第6章所描述的),快照经常会将内容为配置信息的最后一条日志记录的下标作为last included index。当快照生成以后,服务器就会将last included index代表的下标之前的所有日志都清除掉,而且每次生成日志记录以后,都会这么做。

图12:一台服务器将会根据它的日志里面已经提交的日志记录(本例中就是下标为1到5的日志记录),生成一份儿新快照,新快照的内容就是当前状态机的整体状态(本例中就是x=0、y=9,也就是被提交日志记录里的命令被执行后的状态)。这个快照包含的last included index 和last included term分别为5和3,在后续的过程当中,如果说对应的日志记录已经清除了,我们也会知道这个快照的内容是覆盖到下标为6的日志记录之前的状态机状态。

    虽然服务器平时都是自己独立的去生成快照,但是有的时候,leader还是需要将自己的快照传送给那些已经严重落后的追随者的,虽然只是偶尔发生。比如说,如果一个追随者落后很多,而leader在生成快照以后,将还没同步给这台追随者的日志记录给删除了,那么就需要leader将自己当前的快照传送给这个追随者了。幸运的是,这种问题在一般的情况是不会发生的,一个追随者一般都会跟得上leader的日志。但是,那些因为异常而超级慢或者新加入集群的服务器就可能跟不上了。为了让它们及时跟上来,就需要leader将自己最新的快照通过网络传送给它们了。

    leader采用一种新的、叫做InstallSnapshot的RPC请求来讲快照发送给那些极度落后的追随者,请求内容如图13所示。当一个追随者接收到InstallSnapshot请求后,它必须要根据自己当前的日志记录来决定怎么做。一般的情况下,快照的内容肯定是新于追随者的日志内容的,在这种情况下,追随者会丢弃整个日志,用快照的内容整个覆盖状态机的状态,即使原来的日志里面可能会包含一些还未提交的或者与快照冲突的日志记录。而如果快照的内容旧于追随者本身的日志(比如说因为重复发送或者其他错误的原因),那么与快照last included index同下标及之前的日志记录将会被抛弃掉,而比快照新的那些日志记录需要保证继续存在且有效。

图13:InstallSnapshot RPC调用及处理逻辑概述。将快照数据拆分成很多的数据块,然后按顺序发送给追随者,这样就减少了传输时间,就可以起到心跳请求的作用,追随者在收到这样的请求后,就会跟收到心跳请求一样重新开始election timeout的计时了。

    因为追随者是自己生成快照,而不需要经过来自leader的确认,所有快照机制是游离于Raft的强leader属性的(意思应该是在Raft里面,所有的操作都是由leader发起和操控的)。但是我们应该以辩证的角度就考虑这件事情:因为有了leader的帮助,才避免了可能的冲突,从而达成了一致性,因此在生成快照的时候,才会不用考虑可能会有影响一致性的冲突存在。数据还是单方向的从leader流向追随者,追随者只需要对这些数据进行识别处理就可以了。

    我们也考虑了另外一种压缩方案,就是只让leader是生成快照,然后再将快照发送给追随者。但是这种方案有两点不足之处,一个是,将快照信息发送给追随者会占用大量的网络带宽,还会降低快照的处理速度。每台服务器者都已经拥有了所需的日志和相关信息,那么根据自己的状态在本地生成快照的方案和通过网络从leader获取快照的方案相比,肯定前者更优。第二个就是,leder的逻辑会变得更复杂。比如说,为了不阻塞来自客户端的请求,leader需要保证同时去给所有的追随者推送快照,以便缩短时间,就跟推送日志记录一样。

    在生成快照的时候,有两个影响性能的问题需要考虑。一个就是采用什么样的频率去生成快照。如果生成快照太频繁,将会到时磁盘I/O处于繁忙的状态,甚至引起告警。相反,如果太不频繁了,那么长时间的日志堆积就有可能占尽磁盘存储,而且一旦服务器发生了崩溃,在重启以后,就需要花长时间去回放这些日志。一个简单的策略就是设置一个阈值,当日志的大小超过这个阈值得时候,就会生成一份儿新的快照。如果这个阈值设置的要比预期的明显的大,那么在生成快照的时候,磁盘的I/O繁忙度就会小于预期,反之亦然。

    第二需要考虑的问题是,生成一个快照需要耗费很长的一段时间,但我们并不想因此而影响到正常业务操作。解决的办法就是采用copy-on-write技术:新的更新操作在被接收的时候而不会影响到正在被创建的快照。例如:用于功能数据结构(functional data structures)的状态机就支持这种操作。或者我们也可以依赖于底层操作系统提供的copy-on-write的功能(例如 Linux中的fork功能),在内存当中创建整个状态机的快照(我们的实现就是这么做的)。

8 与客户端的交互

    这张主要讲述客户端如何与Raft进行通信,主要包括客户端如何确认哪台服务器是leader和Raft如何实现线性语法(linearizable semantics,关于linearizability可以和serializability一起查询,比较着进行理解)。只要是有关一致性的系统,都会面临这些问题,所以Raft的处理办法与其他的系统很类似。

    Raft的客户端会将所有的请求都发送给leader。当一个客户端刚刚启动的时候,它先随意的连接一台服务器,如果这台服务器不是leader,就会拒接来自客户端的连接,并且告诉客户端,它自己所知到的当前leader的服务器信息(AppendEntries RPC请求里面会包含leader的网路地址信息)。如果leader发生了崩溃,客户端就会察觉到到leader的连接超时了,那么它就会再重新随意连一台服务器,重复上面的动作。

    我们设计Raft的一个主要目的就是实现线性语法(linearizable semantics,也就是说每次操作的执行看起来就像是立马执行了,而且只会执行一次,在接到调用请求和返回结果之间的某个时间点执行掉,在linearizability里面有可能跟其他的调用是并发的,但是也可以调整顺序)。但是我们知道,在Raft里面一条命令有可能被执行多次:例如,leader在提交日志记录之后,将执行结果返回给客户端之前,发生了崩溃,客户端就会重新连接新的leader,并重新发起刚才的请求,这样就会造成命令的二次执行。解决的办法就是给每一个客户端的请求都设置一个全局唯一标识(全部客户端的意思),状态机将会跟踪每一台客户端的最新请求(有全局标识),且记录请求的返回结果。如果接收到请求的时候,发现这条请求已经被处理过了,那么就会再处理这条请求,而是将以前的执行结果直接返给客户端。

    如果发过来的请求是只读操作,那么就不会有写日志的操作。但是,如果没有额外的保障机制,返回给客户端的结果有可能是过期数据,比如说,在leader返回结果给客户端的时候,发生了崩溃,而它的状态内容被新的leader的快照给覆盖掉了,那么先前返回给客户端的结果就有可能是过期数据。线性语法的一个要求就是返回给读请求的结果不能是过期数据(脏读)。为了保证返回给客户端的数据不是过期数据,Raft采用了两种预防措施,而且这两个措施是不依赖于日志的。第一,leader必须保证所有的已提交的日志记录都被执行了,也就说必须有这些记录里包含命令的执行结果信息。Leader Completeness Property 保证了leader能看到所有已提交的日志记录,但是在一个期次启用初期(尤其是没有新日志记录提交的情况下),leader有可能并不知道哪些日志记录已经提交了。为了解决这个问题,我们让leader在选举成功后,立马追加一条命令是空的日志记录到日志里面,这样就能看到所有已提交日志记录了。第二点,leader在将读结果返回给客户端之前,必须要校验一下自己是否还是leader(如果已经有新的leader产生,那么它的数据有可能就是过期数据了)。校验的办法就是,在返回结果之前,这个leader先发一波心跳请求给大部分的服务器。当然,可以基于心跳逻辑做一套定时机制【9】,来保证安全准确性(假设时钟偏移是有界的,bounded clock skew,可以百度 clock skew)。

9 实现和相关评估

    RAMCloud【33】的配置文件管理及它的容灾恢复等的实现都采用了Raft实现的状态机。Raft的实现大概包括2000+行代码,其中不包括测试、注释、空行等信息。源码是免费的的【23】。还有来自其他三方机构的依赖Raft的大概25个实现【34】。当然,各种公司也会开发基于Raft的系统。

    在本章剩下的章节里,我们将从三个方面对Raft进行评估:可理解性、正确性、性能。

9.1 可理解性

    为了衡量Raft是否容易被理解,我们找了一些人来学习Raft和Paxos,他们分别来自在斯坦福大学学习操作系统的学生和来自伯克利学习分布式的学生,有已经毕业的,也有即将毕业的。我们分别为Raft和Paxos录制了两份视频教程,及相关的问答题。Raft的教程内容包含了这边文章除压缩方面外的所有内容。paxos的教程尽量的包含了如果利用paxos来实现状态机的所有内容,有基于single-paxos的,也有基于multiple-degree的,及其一些在实际实现过程需要的优化方案(例如leader选举)。问答题的主要内容是关于算法概念的,也有一些是让同学们对一些具体逻辑进行推导的。每位同学都是学习一份教程,然后解答这份的问答题,然后再学习另外一会儿教程,同样的进行问答题解答。其中一半儿的同学先做paxos的问答题,而另外一部分同学先做的raft的问答题。我们通过参与者的分数来判断raft对于参与者来说,是否更容易理解。

    为了对两者进行公平的比较,在我们选择参与调研的同学时,特地从43个人选名额中选了14个在Paxos方面有一定经验的同学,同时,将paxos的教程延长了14%,争取尽量的详细。为了不引起偏见,我们将所有可能引起偏见的源头都进行了缓和处理,如表1所示。我们的所有资料都是可以进行查阅的【28,31】。

    平均来说,参与者的得分,raft比paxos高4.9个点(将60点以上的刨除掉,raft的平均得分是25.7,paxos的得分是20.8);图14显示他们个自己的得分情况,从平均分布可以看到Raft的分数比Paxos的分数大概高2.5点。

图14:43位参与者的raft与paxos的问答题得分散列图比较。如果raft的得分大于33,那么就表明这位参与者的raft得分较高了

    我们同时建立了一个线性模型来预测学生的分数,它从三个变量因子进行预测:他们答了哪一份儿题,他们以前对paxos的熟悉程度,他们是按照什么顺序学习这两种算法的。模型预测选择paxos问答题的比选择raft问答题的学生人数高12.5点,远远大于4.9点,因为有些同学以前有paxos的一些经验,这就让paxos容易被选择,而raft就没那么幸运了。奇怪的是,虽然选择了paxos,但是他们的得分却比raft的少6.3个点,虽然我们也不知道为什么会这样,但足以说明问题了。

    在完成了问答以后,我们对参与者进行了又一次调研,看看他们觉得哪种算法实现起来或者阐述起来比较容易;结果如图15所展示的。大部分的受调研者表示,相比较其起来,还是raft容易实现和阐述(41个人中有33个人表示)。当然了,主观的感受肯定没有分数更真实,因为有些参与者在我们的主观意识影响下,会觉得raft更容易被理解。

图15:采用5分制对参与者进行调查,左侧的表示,哪一种更容易在一个功能性、保证正确、有效率的系统里被实现。右边的表示,哪一种容易向计算机科学的毕业生生进行阐述。

    raft用户学习的详细讨论在【31】当中。

9.2 正确性

    我们已经对章节5中的一致性算法记性了详细说明,也验证了它的安全正确性。利用TLA+这种论证语言,我们在说明文档【31】中对图2展示定义的进行了更精准的说明。这份儿文档大概有400行,可以作为论证来使用。当然了,这份文档对于真正要实现raft的开发者来说也是有参考价值的。我们已经利用TLA论证系统【7】对Log Completeness Property进行了论证。但是这份儿论证的某些方面还没有进行确认(例如,我们还没有证明这份文档中类型的安全准确性)。另外,我们还写了一份儿完整的关于State Machine Safety属性的论证(它是依赖于单独说明的)也是很准确的(大概有3000字)

9.3性能

    Raft的系统性能跟其他的一致性算法系统差不多,例如paxos。衡量性能的主要是看leader在同步备份一条新的日志记录的时候的性能。raft采用的方式就是采用最少的消息交互(同时将日志记录同步给其它大部分的服务器,这样只需要一个round-trip的时间就够了)。当然,还可以对raft的性能进行改进,例如为了高吞吐、低延迟,我们开可以采用批量的方式或者采用管道传输的方式。各种优化方案在其它算法的文章中都介绍应用过,其中有一些是可以应用到raft里面的,但这是后续的工作。

    通过对我们的Raft实现的的选举算法进行性能压测,我们可以解答两个问题,一个就是raft的选举过程能不能快速的聚焦(应该是是否可以由多台竞选,集中到一台竞选的),二是,当leader发生了崩溃的,能恢复服务的最短时间是多少。

    为了衡量选举的性能怎么样,我们组建了一个由5台服务器组成的集群,然后不停的让其中的leader系统崩溃,然后开始进行计时:从leader发生崩溃,到选出一台新的leader需要多少时间(图16所示)。为了模拟最坏的场景,我们在每次测试的时候,都让服务器拥有不同长度的日志,这样,有些服务器就不适合做leader了。另外,为了能够制造选举脑裂的场景,在关闭leader之前,我们利用测试脚本先触发一轮心跳RPC调用(模拟的相当于leader在崩溃之前,发送AppendEntries RPC请求)。为了测试,所有的leader都是在发送下一波心跳请求前的某个时间发生崩溃,大概是最短eleection timeout时间的一半的时候。所以,服务器不可用的最短时间也就是最小election timeout时间的一半儿。

图16:从发现到替换一台崩溃的leader所用的时间。上面的图表显示的是随机过期时间段小范围发生变化的情况,下面的图表显示的是最小election timeout时间、及其时间段发生变化时的情况。每条线都代表1000次测试(150-150ms的除外,只进行了100次测试)和这些测试进行时所选择的时间段范围;例如:150-155ms表示election timeout只会在150~155毫秒之间进行选择。测试在一个由5台服务器组成的集群上进行的,集群里的广播时间大概为15ms。如果采用9台服务器构成的集群,结果与之类似。

    图16中上面的图表显示,只要一个很小的随机election timeout时间段就足以避免选举过程中产生的脑裂问题了。如果没有随机的election timeout时间,就会产生脑裂很多问题,这样的话,就需要超过10秒钟的时间来完成选举。我们只要将随机时间段的时间间隔设置为5ms,就能有效的降低选举时间,大部分能降低287毫秒。如果我们再把随机时间间隔拉长到50ms,最差的情况也能得到改善:只需要513毫秒(经过1000次测试)。
    图16中下面的图表则显示了,当我们缩短了election timeout的时候,服务的不可能用时间会大大降低.比如,我们将election timeout时间设置为12~24毫秒,平均只需要25ms就可以选举出一台新的leader(最长的一次测试用了152ms)。但是,无限度的降低election timeout与raft对时间的要求是冲突的:如果把election timeout设置的太短,那么就有可能leader在还没有来得及发送心跳之前,其它的服务器已经发起一轮新的选举了。这样会造成不必要的leader选举,也会影响到服务的可用性。我们建议采用一个适中的随机时间段:例如150~300毫秒;这样既可以避免不必要的leader选举,也保证了服务的可用性。

10 相关工作

    现在有很多关于一致性算法的文档或书籍,但是大体上上可以归结为以下几类:

  • Lamport's original description of 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】。
  • Oki and Liskov’s Viewstamped Replication (VR),一个和paxos同时代的一致性算法。最开始的时候,它主要是针对分布式事务进行描述的,但是,在最近的一次版本更新过程当中,它里面的一致性算法被单独拎了出来。VR在很多方面跟Raft都很相似,都是基于leader机制的。

    Raft跟paxos的最大区别就是它的强leader属性:Raft将leader作为一致性算法中的最重要环节,尽可能的将工作都汇集到leader处来处理。这样的方法使得算法更容易理解。例如,在Paxos中,leader的选举跟一致性算法没有多大关系,它只是作为影响性能表现的因素,并不会影响到一致性的达成。这样的话,就需要其它的逻辑来实现一致性:paxos需要包含两套二阶段提交逻辑,一套是用于一致性的,一套是用于leader选举的。相反,Raft将leader选举直接集成到了一致性算法当中,作为二阶段提交中的第一阶段。这样就会让Raft比paxos简单很多。

    跟raft一样,VR和Zookeeper也是基于leader机制的,所有也会利用一些paxos没有的优秀策略。但是Raft的逻辑比Zookeeper和VR还是要简单,因为和它们相比,Raft中非leader的角色做的工作很少。例如:在Raft当中,日志只会从leader以Appendentries的方式单一方向的流出。在VR当中,日志的流向却是双向的(在选举的过程当中,leader既能接收日志,也能发送日志);这就增加了额外的逻辑和复杂性。从公开的信息看,Zookeeper也是双向的,但是实际当中很可能与raft一样,也是单向的【35】。

    和其它的基于一致性算法的日志备份系统相比,raft只有很少的信息类型需要我们关注。我们数了一下VR与Zookeeper关于一致性和成员关系变化相关的信息类型(日志压缩和与客户端交互的信息类型不包含在内,因为这些在任何系统里都是必须的)。VR与Zookeeper每个都定义了10种消息类型,而Raft只定义了4种(2中RPC请求类型,及其对应的结果类型)。虽然Raft的消息内容看起来比较密集,但是它们便于收集。另外,在Zookeeper还有VR的相关描述中,它们选举leader的时候,需要传输整个日志文件,而在实际当中,就有可能需要额外的消息类型来进行优化操作。

Raft采用l强eader的方式虽然简化了逻辑,但是引出了性能方面的问题需要优化。例如,采用非leader的方式,Egalitarian Paxos(EPaxos)达到一个很好的性能【27】。EPaxos会在状态机命令当中加入交互信息,如果一台服务器在提交一条命令的时候,其它服务器提交的命令都知道这条命令存在,那么只需要一轮通信就可以了。但是,如果,同时提交的命令,不知道彼此的存在,那么就需要先进行一轮通信,然后再提交命令。因为每台服务器都可以提交命令,所以在公网里EPaxos的负载很好,延迟很低(此段不是特别的理解)。当然,这是通过添加额外的复杂性才实现的。

    对于重新配置这块儿,已经有很多方式被提了出来,或者已经进行了实现,包括Lamport‘s original proposal【15】,VR【22】和SMART【24】。Raft采用的是join consensus的方式,因为这种方式是借助于一致性协议来实现的,所以在进行配置切换时,就不需要太多额外逻辑。Lamport的以a为基础的方式对raft来说并不适用,因为它的一致性是基于无leader实现的。而相较于VR跟SMART的实现来说,Raft也很有优势,因为在配置的切换过程当中,Raft还能响应来自客户端的正常请求;相反的,VR在切换的过程中,需要停止服务,SMART采用的类似于a的方式限制请求数量。而且,Raft的机制添加的额外逻辑也比较少。

11 结论

    在我们设计一个算法的时候,我们总是以正确性、高可用性、简单性作为主要目标。当然这些目标也是必要的,但是我们觉得算法的可理解性也一样重要。如果一个开发者不能很好的理解算法,对其形成一种直观意识,那么在实际的实现过程当中,上面的目标也就很难实现。

    通过这篇文章,我们很好的解决了分布式一致性的问题,虽然Paxos已经被接受了很多年,但是它的不易理解性也挑战了学生和开发者很多年。我们开发了一种新的算法,Raft,要比Paxos可容易理解得多,同时,我们也觉得,Raft为真正的系统实现提供了很好的基础。可理解性是我们在设计Raft时,一直遵循的目标,这也影响到了Raft的很多实现方式;在设计的过程中,我们也发现我们在重复的使用着一些技术,例如问题拆解的技术方法和减少状态空间的技术方式。这些技术不仅使Raft变得可理解了,也更容易让我们验证Raft的准确性。

12 鸣谢(略)

引用

[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, Sympo-
sium 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 Sym-
posium 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 Implemen-
tation (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 faulttolerant
mechanism for distributed file cache consistency.
In Proceedings of the 12th ACMSsymposium 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 (July
1990), 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 systems.
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 News
32, 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 replication
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, ACMSIGOPS/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] MAZI`E 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).

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., MAZI`ERES, D., MITRA,
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.

点击打开链接

点击打开链接

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值