Bootstrap

分布式一致性算法-Paxos翻译和注解


Paxos是解决不可靠 处理器(不可靠是指处理器可能故障)网络中一致性问题(consensus)的一个协议族。一致性(或者共识)是在一组参与者之间对一个结果达成共识的过程。当参与者或者它们的交互媒介可能发生故障的时候,这个问题变得复杂。

正如Leslie Lamport建议以及Fred Schneider研究的那样,一致性协议是分布式计算状态机复制(state machine replication)方法的基础。状态机复制是将一个算法转换为容错、分布式实现的技术。Ad-hoc(点对点)技术可能会遗留重要的未解决的故障场景。由Lamport等人提出的原则性方法确保了所有场景都会被安全处理。

Paxos协议首先于1989年发布,它是根据在希腊Paxos岛上使用的一个虚构的立法共识系统来命名的,在这个地方Lamport写道,“尽管议员不断地进出议会会议厅”,但仍要议会必须行使其职能。之后在1998年作为期刊文章发布。

Paxos协议族包括了处理器数量、学习商定值之前的消息延迟数量、独立参与者的活动级别、消息发送数量以及故障类型之间的权衡范围。尽管没有明确的容错共识协议可以保证异步网络中的运行(Fischer、Lynch以及Paterson的论文证明了这一结果),但是Paxos保证了安全性(safety – 即consistency一致性),以及保证了那些可能阻止它运行的条件很难被触发。

Paxos通常在需要持久性(durability)的地方被使用(例如,复制一个文件或者数据库),这当中持久的状态量可能很大。该协议尝试即使在一些边界数量的副本无响应时也能继续运作。也有机制来丢弃永久故障的副本或者添加新的副本。

译者注

  1. 处理器:本文中出现的“处理器”都是高度抽象化的,在原文中单词为processor,它并不是指一个CPU,而是指一个处理单元集合,它可以是一个程序,或者一组程序,再或者一个服务器节点。
  2. 消息延迟:一条消息就被称为一个消息延迟(message delay),即消息等同于消息延迟,本文中描述“有xxx个消息延迟”就表示“有xxx条消息”。

历史

一致性的话题早于Paxos协议。在1988年,Lynch、Dwork以及Stockmeyer已经证明了在一个广泛的“部分同步”系统族中一致性(共识)问题的可解性。Paxos协议,它跟在“Viewstamped Replication”中用于在分布式事务上下文中达成一致的协议是非常相似的,Viewstamped Replication首先于1988年由Oki和Liskov发布(最初被提出是作为数据库中的一部分工作的,2012年作为单独的分布式共识算法再次发表)。尽管有这个早前的工作,但是Paxos提供了一种特别优雅的形式,并且包含了一个容错分布式共识协议的最早安全性证明。

可重复配置的状态机(Reconfigurable State Machines)跟之前的在可靠组多播协议(Reliable Group Multicast Protocols)上的工作有很强的联系,可靠组多播协议支持动态组成员,例如Birman在1985和1987年在虚拟同步组播协议(virtually synchronous gbcast)上的工作。但是gbcast(组播)在支持持久性和定位分区故障方面是不常用的。大多数多播协议缺乏这些属性,它们需要状态机复制模式的实现。这点在Lamport、Malkhi和Zhou的论文中详尽阐述了。

Paxos协议是形式为对崩溃故障统一一致意见的问题的理论类解决方案的成员。这个问题的更低边界已经由Keidar和Shraer证明了。Derecho,一个云尺度状态机复制的C++软件库,提供了一种Paxos协议,它已经跟自管理虚拟同步成员(self-managed virtually synchronous membership)整合在了一起。这个协议匹配Keidar和Shraer最优边界,并且有效映射到现代远程DMA(RDMA)数据中心硬件上(如果RDMA不可用那么使用的是TCP)。

假设

为了简化paxos的介绍,先明确下面的假设和定义。扩展适用性的技术在文献中是已知的,因此本文中不涉及。

处理器

  • 处理器是以任意速度运行的。
  • 处理器可能会遇到故障。
  • 有稳定存储的处理器可能会在故障后重新加入到协议(遵循崩溃-恢复故障模型)。
  • 处理器不会串通、欺骗或者尝试破坏协议。(即拜占庭问题不会发生。参见容忍由任意/恶意过程行为导致的故障的一种解决方案 – 拜占庭Paxos。)

网络

  • 处理器可能向任意其他处理器发送消息。
  • 消息是异步发送的,并且可能采用任意长度来传递。
  • 消息可能丢失,被重排序或者被复制。
  • 消息是无损传动的(即,拜占庭故障不会发生。参见容忍来自消息通道的任意/恶意行为导致的消息损坏的一种解决方 – 拜占庭Paxos

处理器数量

通常,一致性算法可以使用n=2F+1个处理器来运行,尽管同时有任意F个处理器故障也能运行:换句话说,没故障的处理器数量必须严格大于故障处理器的数量。但是,使用重新配置(reconfiguration),可以使用任意总故障数量也幸存的协议,只要同时故障不超过F个。

角色

Paxos在协议中通过处理器的角色来描述它们的动作:客户端(client)、接收者(acceptor)、提议者(proposer)、学习者(learner)以及领导者(leader)。在典型的实现中,单个处理器可能同时扮演一个或者多个角色。这不影响协议的正确性——在协议中通过合并角色来优化延迟或者消息的数量是常见的。

  • 客户端(client): 客户端发起请求到分布式系统,并且等待回复。例如,在一个分布式文件服务器中对一个文件的写请求。
  • 接收者(acceptor,或者投票者—voters): 接收者在协议中作为容错的“记忆(memory)”。接收者们被归集到称为仲裁(quorums)的组中。任意发送到接收者的消息必须发送给一个仲裁组的接收者。除非接收到来自一个仲裁组的每个接收者的一个消息拷贝,否则任意接收到的来自一个接收者的消息会被忽略。
  • 提议者(proposer): 提议者主张客户端的请求,尝试说服接收者同意客户端的请求,并且在当冲突发生时,作为协调者来(coordinator)使协议继续往下运行。

译者注
协调者(coordinator)实际上是一个提议者(proposer)

  • 学习者(learner): 学习者是作为协议的复制因子。一旦客户请求由接收者们达成统一意见之后,学习者就可以采取行动了(例如:执行这个请求并且发送回复给客户端)。为了提升处理过程的可用性,可以添加额外的学习者。
  • 领导者(leader): Paxos需要一个杰出的提议者(叫做领导者)来维持运行。很多处理器可能会相信他们自己是领导者,但是只有在它们中的一个被最终选择为领导者时,协议才会保证运行。如果两个处理器相信它们自己是领导者,它们可能会继续提议冲突更新,从而卡住协议。但是,在这种情况中,安全性属性是仍然保持的。

译者注
领导者(leader)实际上是一个提议者(proposer)。

仲裁组

译者注
仲裁组(Quorums),抑或是其他地方翻译的“法定人数组”,是一个参与者的集合。本文统一翻译成“仲裁组”。

仲裁组,通过确保至少有一些幸存处理器保留了对结果的了解,以此来表达Paxos的安全性属性(或者一致性)。

仲裁组被定义为接收者集合的子集,这样的任意两个子集(即任意两个仲裁组)共享至少一个成员。通常,一个仲裁组是参与的接收者中的任意大多数成员。例如,给定接收者集合{A, B, C, D},一个大多数的仲裁组可以是任意三个接收者:{A, B, C},{A, C, D},{A, B, D},{B, C, D}。更普遍地,会给接收者分配任意正值的权重值;在这种情况中,一个仲裁组可以定义为:带有权重总和大于所有接收者总权重总和一半的接收者的任意子集。

提议号和协议值

译者注
协议值(agreed value),是提议者发起提议的值,接收者们会对该值进行协商,最终达成一致意见。即,协议值是分布式系统中需要达成一致意见(一致性)的值。

每次尝试定义一个协议值v是由提议(proposal)来执行的,提议可能会也可能不会被接收者所接收。每个提议是为给定提议者(Proposer)唯一编号的。例如,每个提议可能的格式是(n, v),其中n是这次提议的唯一标识,即提议号v是实际的提议值,即协议值。这个跟已编号提议对应的值,可以被计算来作为运行Paxos协议的部分,但是不是必须的。

安全性和活跃度属性

为了保证安全性(safety,也叫做一致性“consistency”),Paxos定义了三个属性,并确保始终保留前两个属性,而不考虑故障的模式:

  • Validity(有效性,或者非简单性“non-triviality”)
    只能选择和学习提议的值。
  • Agreement(一致意见,或者一致性“consistency”,或者安全性“safety”)
    不存在两个不同的学习者会学习不同的值(或者,不存在超过1个的决定值,即在一次协商中不存在超过1个的协议值被达成一致意见)。
  • Termination(终止,或者活跃度“liveness”)
    如果已经提议了值C,那么最终学习者L将会学习一些值(如果足够的处理器保持未故障的话)。

注意,Paxos不保证会终止(terminate),因此它没有活跃度属性(liveness)。这是由“Fischer Lynch Paterson不可能结论(FLP)”所支持的,它说明了一致性协议只能有安全性(safety)、活跃度(liveness)以及容错(fault tolerance)三个属性中的两种。由于Paxos的关注点是确保容错以及保证安全性,所以它不能也保证活跃度。

典型部署

在大多数Paxos的部署中,每个参与的处理器扮演三个角色:提议者(Proposer)、接收者(Acceptor)以及学习者(Learner)。这在没有牺牲正确性的同时,大大减小了消息的复杂性:

在Paxos中,客户端发送命令给领导者。在常规操作期间,领导者接收客户端的命令,给它分配一个新的命令号i,然后把消息发送给一个接收者进程集合,以此开始第i个一致性算法实例。

通过合并角色,协议“坍塌”为一个高效的客户端主副本(client-master-replica)风格的部署,这是数据库社区的典型部署。Paxos协议的好处(包括具有合并角色的实现)是保证它的安全性属性。
典型实现的消息流在Multi-Paxos小节中有介绍。

基础Paxos

这个协议是Paxos家族中最基础的协议。每个基础Paxos协议的“实例”(或者“执行体”)判定单个输出值。该协议会执行几个回合(round)。一个成功的回合有两个阶段:阶段1(分为a和b两部分)以及阶段2(分为a和b两部分)。参见下面对这些阶段的描述。记住,我们假设了一个异步模型,因此一个处理器可能在一个阶段的时候,另一个处理器可能在另一个阶段中。

阶段1

阶段1a:准备(Prepare)

提议者(Proposer)创建一个消息,这我们称为“准备(Prepare)”消息,由一个数字n标识。注意n不是要提议的值,也不是可能达成一致意见的值,它只是一个数字,唯一标识由该提议者创建的这个初始消息(会被发送给接收者的消息)。这个数字n必须大于之前由该提议者创建的任意Prepare消息中所使用的数字。然后,提议者将这个包含数字n的Prepare消息发送给一个仲裁组的接收者。注意,这个Prepare消息只包含数字n(即,它不必包含通常由v表示的提议值)。这个提议者判定谁是在这个仲裁组中。如果提议者不能跟至少一个仲裁组的接收者通信,那么它不应该初始化Paxos。

阶段1b:承诺(Promise)

接收者(Acceptor)等待来自任意提议者的准备消息。如果一个接收者收到了一条准备消息,这个接收者必须查看刚刚接收到的这个准备消息的标识号n。有两种情况:

  1. 如果n大于所有之前该接收者收到的来自任意提议者的提议号,那么该接收者必须返回一个消息(称为一个“承诺‘Promise’”)给该提议者,以忽略所有将来有比n小的提议号的提议。如果该接收者在过去某个时间点已接受(accepted)了一个提议,它必须在给这个提议者的回复中包含这个之前的提议号,表示为m,以及对应接受的值,表示为w。
  2. 否则(即,n小于或等于该接收者之前接收到的来自任意提议者的提议号),该接收者可以忽略掉这个接收到的提议。这种情况中,接收者没有必要做出回复来保持Paxos的运作。但是,出于优化的目的,发送一个拒绝(Nack-否定)回复会告诉这个提议者他可以停止用提议号n来建立共识的尝试了。

阶段2

阶段2a:接收(Accept)

如果提议者(Proposer)接收到了来自一个仲裁组中大部分接收者的承诺(Promise)消息,那么它就需要给它的提议设置一个值v(提议值)。如果任意接收者已经在之前接受过提议了,那么它们会把它们的值(包括提议号和提议值)发送给这个提议者。该提议者现在必须要设置它的提议值v,将设置为这些接收者报告的最高提议号相关联的值,我们称之为z。如果目前所有接收者都没有接收过提议,那么该提议者可以选择它原始想要提议的值,为x。

提议者(Proposer)发送一个接收(Accept)消息(n, v)到一个仲裁组的接收者,消息中包含提议值v,以及提议号n(跟之前发送给接收者的Prepare消息的提议号是一样的)。因此这个接收消息要么是(n, v=z)(即接收者之前已经接受过提议),要么是(n, v=x)(即所有接收者之前没有接收过提议)。

这个Accept消息应该被解释为一个“请求”,就像“请接收这个提议”。

译者注:从1b和2a的描述看,可以得出这样的结论——如果有接收者之前已经接受过提议,那么现在的提议者就不能提交自己的提议值了,提交的仍是之前已经被接受的那个值。

阶段2b:已接收(Accepted)

如果一个接收者接收了来自提议者的Accept消息(n, v),当且仅当在该接收者尚未承诺(Promise,在Paxos协议的阶段1b中)只考虑标识符大于n的提议时,它才必须接受。

译者注
注意这里描述的“只考虑标识符大于n”,是大于,通常在阶段1b中的Promise消息承诺的是会忽略提议号小于n的提议,而没有承诺“只考虑标识符大于n”。

  • 如果这个接收者尚未承诺(阶段1b)只考虑标识符大于n的提议,那么它应该注册值v(刚刚接收的Accept消息中的值v)为已接收的值(协议的已接收值),并且发送一个已接收(Accepted)消息给该提议者以及每个学习者(学习者通常可能是提议者自己)。
  • 否则,接收者会忽略掉这个Accept消息或者请求。

注意一个接收者会接收多个提议。这是可能发生的,当另外的提议者,它不知道当前这个正在被判定的新值,用一个更高的标识号n来开始了一个新的回合(round)。在这种情况中,该接收者会承诺并且在之后接收了这个新的提议值,尽管它已经在早前接收了另外的值。在存在某些故障的情况下,这些提议甚至可能有不同的值。但是,Paxos协议会保证接收者将最终对单个值达成一致意见。

回合失败的时候

当多个提议者发送冲突的Prepare消息,或者在提议者未接受到一个仲裁组的答复(Promise或者Accepted消息)的时候,round会失败。在这些场景中,必须用更高的提议号来启动另外的回合(round)。

Paxos可以被用来选举一个领导者

注意,当接收者接收一个请求的时候,他们也知道了提议的领导(leader)。因此,Paxos可以用来在一个节点集群中选举一个领导者。

基础Paxos消息流的图示

下面的图形表示了多个应用基础Paxos协议的场景。一些场景展示了基础Paxos协议是如何处理分布式系统某些(冗余)组件故障的。
注意,在首次构建提议时,承诺消息中返回的值是“null”(因为没有接收者在这个回合之前已经接收过一个值)。

没有故障情况的基础Paxos

在下图中,有1个客户端(client),1个提议者(proposer),3个接收者(acceptor)(例如,仲裁组的大小是3)以及2个学习者(learner)(由2 条垂直线表示)。这个图展示了第一个回合是成功的场景(例如,网络中没有过程失败)。
在这里插入图片描述
这里的V是(Va, Vb, Vc)中最新的那个(译者注:这句的描述跟2a的描述是有差异的,V的值应该是Promise报告的所有提议中具有最高提议号的那个提议的提议值,或者是提议者自己的提议值)。

译者注
在这里插入图片描述
如上图标识所示,有7个步骤:

  1. 有1个客户端(Client),1个提议者(Proposer),3个接收者(Acceptor,仲裁组大小为3),2个学习者(Learner)。
  2. 客户端向提议者发送一个请求。
  3. 提议者发送提议号为1的Prepare消息给仲裁组中的所有Acceptor。
  4. 仲裁组中的Acceptor向提议者回复Promise消息。
    4.1 承诺从此以后自己会忽略提议号小于1的所有提议。
    4.2 并将过去已经接受(注是接受,而不是接收)的提议的提议号和提议值附加到Promise消息中发送给提议者。
  5. 提议者向仲裁组中的Acceptor发送Accept消息,并且附带上本次提议的提议值V。
    V的值有两种情况,
    如果在步骤4中,Va、Vb和Vc都是空值(即接收者之前没有接受过提议),那么V的值是本次提议者自己的提议值。
    如果不在4中,Va、Vb和Vc存在有不为空(即有接收者在此之前接受过提议),那么V的值为Va、Vb和Vc拥有最高提议号的那个。
  6. Acceptor接受提议值V,并向提议者和学习者发送Accepted(已接受)消息,表示自己已经接受了提议值V。
  7. 当收到大多数Acceptor的已接受消息后,学习者执行对新提议值V的学习,然后回复客户端,值V已经达成一致意见了。

基础Paxos中的错误场景

最简单的错误场景是一个接收者(Acceptor)故障了(当一个仲裁组的接收者保持是活动的)以及一个冗余学习者的故障。在这些场景中,协议不需要“恢复”(例如,它仍然是能成功的):不需要额外的回合或者消息,如下所示(在接下来的两个图展示的场景中)。

一个接收者故障时的基础Paxos

在下图中,仲裁组中一个接收者故障,这样仲裁组大小变为2。在这种情况中,基础Paxos协议仍然是成功的。
在这里插入图片描述

译者注

  1. 在上图中,故障了一个接收者,如在4中感叹号表示的接收者所示。
  2. 在步骤5中,提议者只收到2个Acceptor的Promise,因为2个已经是仲裁组(大小为3)的半数以上(即大多数),所以此时提议者认为大多数Acceptor已经做好准备了。
  3. 于是从6开始向仲裁组的Acceptor发送Accept消息。
  4. 在步骤7中收到大多数(3个中的2个)Acceptor的Accepted消息,值就达成协商了。

一个冗余学习者故障时的基础Paxos

在下面的场景中,一个学习者(冗余的)故障了,但是基础Paxos协议仍然是成功的。
在这里插入图片描述

一个提议者故障时的基础Paxos

在这个场景中,一个提议者在提议一个值之后,并且在达成一致返回之前故障了。尤其它在Accept消息中间故障,这样在仲裁组中只有一个接收者收到这个值。与此同时,一个新的领导者(它实际是一个提议者)被选出(这个过程不详细展示出)。注意,在这个场景中会有2个回合(回合垂直执行,从顶部到底部)。
在这里插入图片描述

译者注

  1. 步骤1~4中,一切正常,客户端向提议者发送了一个请求;然后提议者向仲裁组中的Acceptor发送了提议号为1的Prepare消息;然后所有Acceptor向提议者发送了Promise消息。
  2. 步骤5中,提议者准备向仲裁组中的Acceptor发送Accept消息,在Accept消息中携带了提议值V。
    当只有一个Acceptor收到该Accept消息之后,提议者故障了。(记住该Acceptor已经收到了提议,该提议的提议号为1,值为V)。
  3. 步骤6中,新的领导者被选举出来。
  4. 步骤7中,新的领导者(实际上是一个提议者)将提议号为2的Prepare消息发送给仲裁组的Acceptor。
  5. 步骤8中,Acceptor向提议者发送Promise消息,由于其中一个Acceptor已经收到原来那个提议者的提议,于是该Acceptor会将这个提议的提议号和提议值附加在Promise消息中发送给现在的提议值(即现在的leader)。
  6. 步骤9中,提议者发现有接收者的Promise消息中存在已接收的提议,于是在准备的Accept消息中设置提议值为之前已接收的值V。并且将提议号设置为2(1+1)。将Accept消息发送给仲裁组的Acceptor。
  7. 步骤10中,所有接收者接收Accept消息后,发送Accepted消息给提议者和学习者。
  8. 步骤11中,学习者学习协议值,回复客户端。

多个提议者冲突时的基础Paxos

最复杂的场景是在多个提议者相信自己是领导者的时候。例如,当前的领导者可能故障,并且在过一会儿后恢复,但是其他提议者已经重新选举了新的领导者。恢复后的领导者还不知道这个情况,并且尝试开始一个回合,这和当前的领导者就冲突了。在下面的图中,展示了4个不成功的回合,但是可能会更多这样的回合(如图表底部展示的那样)。
在这里插入图片描述

译者注

  1. 1~4步骤是一切都正常的时候的过程:客户端发起请求;领导者把提议号为1的Prepare消息发送给仲裁组的Acceptor;Acceptor向领导者回复Promise消息,由于这些Acceptor都是第一次接收提议,所以这里的Promise消息中没有附带已接收的提议信息(都为null)。
  2. 步骤5,提议者(或者领导者)发生故障。
  3. 步骤6,选举出了新的领导者,这个新领导者知道最后的提议号为1。
  4. 步骤7,新领导向仲裁组的Acceptor发送提议号为2的Prepare消息。
  5. 步骤8,所以Acceptor向新领导者回复提议号为2的Promise消息,并且没有已接收的提议信息。
  6. 步骤9,故障的领导者恢复了。
  7. 步骤10,恢复的领导者尝试向仲裁组所有Acceptor发送提议号为2的Prepare消息。
  8. 步骤11,由于阶段1b的规定,接收者此时收到的Prepare消息的提议号满足小于或者等于在步骤7中收到的提议号2,所以接收者向消息的提议者,即恢复的领导者发送Nack(2)拒绝接收消息。
  9. 步骤12,恢复的领导者尝试向仲裁组所有Acceptor发送提议号为3的Prepare消息。
  10. 步骤13,所有Acceptor发现提议号满足大于所有已接收的提议号,于是向提议者回复提议号为3的Promise消息,并且没有附加已接收的提议信息。
  11. 步骤14,之前在步骤8中收到所有Acceptor发回的提议号为2的Promise消息,并且没有已接收的提议信息,于是在这里新领导者创建了提议号为2,协议值为自定义值Va的Accept消息,发送给所有接收者。
  12. 步骤15,接收者发现刚刚接收的Accept消息的提议号小于了当前最大提议号3,于是向新领导者发送了拒绝接收消息,并告知当前最大提议号为3,消息为:Nack(3)
  13. 步骤16,新领导者使用提议号4向所有接收者发送Prepare消息
  14. 步骤17,接收者们发现提议号4大于当前的提议号3,于是向新领导者发送Promise消息。
  15. 步骤18,恢复的领导者使用提议号3向接收者发送Accept消息。
  16. 步骤19,接收这项恢复的领导者发送决绝Nack(4)消息。
  17. 步骤20,更多的消息轮回……

Multi-Paxos

Paxos典型的部署需要连续的协商值流,作为对分布式状态机的命令。如果每个命令是基础Paxos协议单个实例的结果,那么会导致大量的开销。

如果领导者是相对稳定的,阶段1就变得没那么必要了。因此,为将来具有相同领导者的协议实例是可以跳过阶段1的。
为了做到这一点,会在每个值中包含回合号(round number),字母I表示,它在每个回合中会由相同的领导者递增。Multi-Paxos将无故障消息延迟(提议到学习)从4个延迟减少到2个延迟。(译者注:无故障时的消息延迟有4个,为Prepare、Promise、Accept和Accepted,现在跳过阶段1,就只剩Accept和Accepted了。)

Multi-Paxos消息流的图像表示

没有故障的Multi-Paxos

在下图中,只有一个基础Paxos协议的实例(或“执行体”),有一个初始的领导者(一个提议者)。注意一个Multi-Paxos是由几个基础Paxos协议的实例组成的。
在这里插入图片描述
这里的V=(Va, Vb, Vc)的最新的那个。

译者注
上图中提议号为N,在每个消息中附加了一个回合号I,其余跟基础Paxos是一样的。

阶段1可以被跳过时的Multi-Paxos

在这种情况中,基础Paxos协议的后续实例(由 I+1表示)使用相同的领导者,因此跳过由Prepare和Promise子步骤组成的阶段1(在这些基础Paxos协议的后续实例中的)。注意这个领导者应该是稳定的,它不应该崩溃或者改变。
在这里插入图片描述

译者注:为什么可以跳过阶段1呢?

  • 首先,阶段1包含Prepare和Promise消息,实际上是提议者(领导者)向仲裁组接收者查询“我准备用提议号N发起值协商了,可以不?”,然后接收者要么回复“你可以用提议号N发起提议了,我发誓(我承诺)”。
  • 其次,由于在Multi-Paxos中,提前约定了领导者是不变的,相同的,那么在这个领导者执行过一次阶段1之后,再向仲裁组发送消息时,就没有必要再走阶段1了,因为领导者已经知道什么样的提议号是会被允许的。

角色合并时的Multi-Paxos

一个Multi-Paxos的常见部署是将提议者、接收者以及学习者的角色统一为“服务器”。这样,最后只有“客户端”和“服务器”两个角色。

下图展示了第一个基础Paxos协议的“实例”,将提议者、接收者以及学习者统一为单个角色“服务器”的情况。
在这里插入图片描述

译者注:这里描述的是Multi-Paxos的第一个基础Paxos协议的实例(或者执行体),它是包含阶段1的(即Prepare和Promise消息)。在这个实例之后的实例(前提是领导者不变)就可以跳过阶段1了。

角色合并且领导者稳定时的Multi-Paxos

在基础Paxos协议的后续实例中(注:第一个实例之后的实例),有跟之前该基础Paxos协议实例具有相同领导者的,阶段1可以被跳过。
在这里插入图片描述

优化

一些优化方案可以应用来减少消息交换的数量,提升协议的性能,等等。这些优化方案中的一些如下:

  • 通过让单个杰出学习者在发现有值被选中时通知其他学习者,可以节省额外消息延迟的开销。然后接收者只把Accepted消息发送给这个杰出学习者。在大多数应用程序中,领导者和杰出学习者的角色是由同一个处理器来执行的。

译者注:领导者(leader)是一个提议者(proposer),也可以是一个学习者(learner)。这在阶段2b中也有相关描述“接收者会发送一个已接收(Accepted)消息给该提议者以及每个学习者(学习者通常可能是提议者自己)。”

  • 领导者可以把Prepare和Accept!消息只发送给一个仲裁组的接收者。只要所有在这个仲裁组中的接收者正在工作,并且可以跟该领导者和学习者通信,那么对于那些不在这个仲裁组中的接收者就没必须做任何事了。
  • 接受者不用关心什么值被选中了。他们简单地对Prepare和Accept!消息做出回复,以确保,不考虑故障的情况,只会有单个值会被选中。但是,如果一个接收者学习(知道)了什么值被选中,那么它可以把这个值存储到稳定的存储中,并且从这里删除它之前存储的其他任意信息。如果在此之后这个接收者接收了一个Prepare或者Accept!消息,那么作为执行它的阶段1b(Promise)或者阶段2b(Accepted)动作的替换,它可以简单地通知领导者这个被选中的值。

译者注:即,如果接收者已经知道选中的值,那么可以不发送Promise或者Accepted消息,而直接通知领导者选中的值是多少;或者在Promise以及Accepted消息中附带上这个选中的值。

  • 作为发送值v的替换,领导者可以在它Accept!消息中向一些接收者发送v的hash值。如果学习者收到来自一个仲裁组接收者的要么v要么v的hash的Accepted消息,并且这些消息中至少有一个包含v而不是它的hash,那么它就能知道v被选择了。但是,一个领导者会收到Promise消息,这个消息告诉了它值v的hash,而没有告诉它v的实际值,而值v是它在阶段2a(Accept!)中必须要使用的。如果这发生了,那么这个领导者直到跟一些知道v的处理器通信后,它才能执行它的阶段2a动作。
  • 提议者只能把提议发送给领导者,而不是其他协调者(注:协调者也是提议者)。但是,这需要领导者选举算法的结果被广播给提议者,这可能是开销大的。因此,最好让提议者把提议发送给所有的协作者。(在这个场景中,只有这些协作者自己需要知道谁是领导者。)
  • 作为每个接收者发送Accepted消息给每个学习者的替换,接收者可以把它们的Accepted消息发送给领导者,领导者可以在值被选定时通知所每个学习者。但是这增加了一个额外的消息延迟。

译者注:为什么会增加一个额外的消息延迟呢?这是由于原本Acceptor的Accepted消息会广播给所有学习者和领导者(提议者),假设有n个Acceptor,那么每个Acceptor发出一条广播消息,就有n个消息延迟。这n个消息延迟就能通知到领导者和所有学习者。而现在,n个消息延迟是定向发送给领导者的,当领导者通过这n个消息知道了选中的值以后,还需要一条消息来通知所有学习者,这条消息就是多出来的那个额外消息延迟。

  • 最后,观察到阶段1对于回合1是非必须的,回合1的领导者可以通过发送带有任意提议值的Accept!消息来开始这个回合。

译者注:注意是Accept!消息,该消息是阶段2a的消息,即,是跳过了阶段1的,领导者直接从阶段2就能开始回合1。

廉价Paxos

廉价Paxos(Cheap Paxos)通过在每次故障后动态重新配置,以此来扩展基础Paxos,以实现在F+1个主处理器和F个备用处理器的系统中容忍F个处理器故障。

这种处理器需求的减小是以牺牲活跃度(liveness)为代价的。如果过多主处理器在短时间内故障,系统必须停止,直到备用处理器能够重配置系统为止。在稳定期间,备用处理器不会参与到协议中。

  • 在只有两个处理器p和q的系统中,一个处理器不能区分故障是其他处理器的故障还是通信介质的故障。这需要第三个处理器的参与。但是,这个第三处理器没有必要参与到选择命令序列中。它只是在p或者q故障时才必须采取行动,在这之后,当要么p,或者要么q能继续靠自己运行系统的时候,它就不做任何事了。这个第三处理器因此可以是小的/慢的/便宜的硬件设备,或者一个主要用于其他任务的处理器。

译者注:注意上面这句话“这个第三处理器因此可以是小的/慢的/便宜的硬件设备,或者一个主要用于其他任务的处理器”,这就是Cheap Paxos的Cheap的含义所在,即在Paxos系统中会有一些廉价的处理器设备,它们会在需要的时候参与到Paxos中,然后当系统正常后就不参与Paxos协议了。

消息流:廉价Multi-Paxos

一个涉及到三个主接收者、一个备用接收者以及仲裁组大小为3的例子,下图展示了一个主接收者故障以及后续的重配置过程:
在这里插入图片描述

快速Paxos

Fast Paxos(快速Paxos)扩充了基础Paxos,以达到减少端到端的消息延迟。在基础Paxos中,从客户端请求到学习之间的消息延迟是3个。Fast Paxos允许2个消息延迟,但是需要(1)系统由3f+1个接收者组成,以容忍最多f个故障(而不是经典的2f+1),以及(2)需要客户端把请求发送到多个目的地。

直观地,如果领导者没有提议的值,那么客户端会直接发送Accept!消息给接收者。接收者会跟在基础Paxos中一样做出回应,发送Accepted消息给领导者和每个学习者,以此来完成从客户端到学习者之间只需要2个消息延迟。

如果领导者检测到冲突,它会发送Accept!消息来开启新回合,该消息会照常被Accepted,以此来解决这个冲突。这个协调恢复技术需要从客户端到学习者之间的4个消息延迟。

最后的优化是在领导者事先指定了一种恢复技术的时候,让接收者自己执行冲突恢复。这样,在没有用协调冲突恢复(协调恢复技术)时可以只有3个消息延迟(如果所有学习者也是接收者,那么只需要2个消息延迟)。

译者注
本小节中,

  • 第一段,基础Paxos是可以跳过阶段1的,所以只剩下阶段2,阶段2有两个消息延迟,一个Accept!消息以及一个Accepted消息。加上客户端发起的请求,基础Paxos总共就是3个消息延迟。从客户端到学习者的三个消息的流程为:1) 客户端发起请求;2) 提议者(即领导者)向接收者发送Accept!消息;3) 接收者向学习者以及提议者发送Accepted消息。
  • 第二段,描述从客户端到学习者之间只需要2个消息延迟:1) 客户端直接给接收者发送Accept!消息;2) 接收者向领导者(提议者)和学习者发送Accepted消息。详见“消息流:快速Paxos,没有冲突”小节。
  • 第三段,描述协调恢复技术这个概念,英文为coordinated recovery technique,它是领导者执行的,当领导者发现冲突,会发送Accept!消息来开启一个新回合,在这个回合中解决冲突。那么,在这种有冲突的情况下,从客户端到学习者之间会有4个消息延迟:1) 客户端向接收者发送Accept!消息;2) 接收者发送Accepted消息;3) 领导者检测到冲突,发送一个Accept!消息到接收者;4) 接收者向领导者和学习者发送Accepted消息。详见“消息流:快速Paxos,冲突的提议”小节图1。
  • 第四段,描述一种优化方案:不使用第三段所述的需要领导者执行的“协调恢复技术”,领导者指定了冲突由接收者自行解决。这时从客户端到学习者之间在有冲突时也只需要3个消息延迟,即1) 客户端向接收者发送Accept!消息;2) 接收者广播Accepted消息(发向领导者、学习者以及其他接收者),在这个过程中会发现冲突;3) 接收者向领导者和学习者发送Accepted消息,解决冲突并通知最后的协议值。详见“消息流:快速Paxos,冲突的提议”小节的图2。

消息流:快速Paxos,没有冲突

在这里插入图片描述

译者注:上图为只需要2条消息延迟就能从客户端到学习者的Fast Paxos。即,1) 客户端直接向接收者发送Accept!消息;2) 接收者向领导者和学习者发送Accepted消息。

消息流:快速Paxos,冲突的提议

用协调恢复技术来解决冲突提议。注意:这个协议没有指定如何处理丢弃的客户端请求。
在这里插入图片描述

译者注:如图,上面过程中丢弃了客户端对V的提议请求。其中,4个消息延迟如下:

  • 1步骤中,两个不同客户端分别向接收者发送了Accept!消息,分别提议V和W值。
  • 2步骤中,两个接收者向领导者、学习者以及其他接收者广播接受值V的Accepted消息。另外两个接收者则广播的是接受值W的Accepted消息。这样领导者会收到不同的Accepted消息,检测到冲突。
  • 3步骤中,领导者发送Accept!消息,选择了值W来提议,丢弃了值V。以此来开始一个新的回合来解决客户端的冲突提议(丢弃了值V的提议)。
  • 4步骤中,接收者广播接受值W的Accepted消息,发送给领导者、其他接收者以及学习者。

下面图2为:没有用协调恢复技术时,有冲突的提议:
在这里插入图片描述

译者注:如图,在没有使用“协调恢复技术”时,即让接收者自行解决冲突时,Fast Paxos只需要3个消息延迟:

  • 1步骤中,两个不同客户端分别向接收者发送了Accept!消息,分别提议V和W值。
  • 2步骤中,两个接收者向领导者、学习者以及其他接收者广播接受值V的Accepted消息。另外两个接收者则广播的是接受值W的Accepted消息。这样接收者会发现跟自己不一样的Accepted消息,检测到冲突。
  • 3步骤中,接收者统一发出接受值W的Accepted消息,解决冲突,并通知领导者和所有学习者。上图没有给出为什么接收者会给出统一的选择,我们可以猜想一个简单的选择方案:选择最后收到的那个值(最新的那个值),等等。

消息流:没有协议恢复技术、角色合并的快速Paxos

(合并了接收者/学习者角色)
在这里插入图片描述

广义Paxos

广义上的共识探讨的是复制状态机(the replicated state machine)的操作和实现它的共识协议之间的关系。主要的发现涉及到可以以任意顺序应用冲突提议的时候,对Paxos的优化。例如当提议的操作对于状态机而言是可交换操作(commutative operations)的时候。在这些场景中,冲突的操作可能都被接受(因为操作是可交换的),这就避免了在解决冲突和重新提议被拒绝的操作所需的延迟。

这个概念被进一步概括为不断增长的可交换操作序列,其中一些已知是稳定的(因此可以被执行)。该协议跟踪这些序列,以确保与所有已提交的操作序列不能交换的操作,使它们变得稳定之前,这些已提交的操作序列是稳定的。

译者注
Generalized Paxos,翻译为广义Paxos,广义的含义主要涉及到对“冲突”提议的冲突解决过程中。如果“冲突”的提议之间是可交换的,即交换它们的顺序,不影响提议的结果,那么实际可判定为“不冲突”,直接接收其中的一个序列,这样会节省解决冲突的消息延迟。

稳定(stable)的含义:执行顺序被确定下来。当两个操作(两个提议的操作)以不同顺序执行时,会得到不同的结果。例如对文件相同偏移数据的Read和Write操作,1) Read先执行然后Write执行,2) Write先执行然后Read执行,两次Read获取到的数据是不一样的。这样的Read和Write操作就是不稳定的,只有确定了它们的执行顺序,它们才是稳定的。再例如,对文件1的Read操作和堆文件2的Read操作,这两个操作不管以怎样的先后顺序执行,它们都互不影响,那么这两个操作的任意执行序列都是稳定的。

示例

为了解释Generalized Paxos(广义Paxos),下面的示例展示了两个同时执行的客户端与一个实现了在两个不同寄存器A和B上读/写操作的复制状态机之间的消息流。
在这里插入图片描述
注意表中的X表示不可交换的操作。
一个可能的操作序列:

<1:Read(A), 2:Read(B), 3:Write(B), 4:Read(B), 5:Read(A), 6:Write(A)>

由于5:Read(A)跟3:Write(B)和4:Read(B)都交换了,一个可能的等价于之前顺序的置换如下:

<1:Read(A), 2:Read(B), 5:Read(A), 3:Write(B), 4:Read(B), 6:Write(A)>

在实战中,交换只会在操作被同时提交时发生。

消息流:广义Paxos(示例)

下面是没有展示Response过程。注意:这里的简略消息与之前的消息流不同,这是因为该协议的细节原因,参见这里(Turner, Bryan [2007]. “The Paxos Family of Consensus Protocols”, WEB site: http://www.fractalscape.org/2007/10/01/paxos-family.html)的完整讨论。
在这里插入图片描述

性能

上面的消息流向我们展示了Generalized Paxos可以利用操作语义来避免当网络的自发顺序失败时的冲突。这使得该协议在实践中比Fast Paxos更快。但是,当冲突发生时,Generalized Paxos需要两个额外的回合过程来进行恢复。这种情形在上面用WriteB和ReadB操作进行了说明。

在常规场景中,这样的回合过程是不可避免的,这是由于在一个回合期间可能接收多个命令这个事实。这使得在冲突很频繁时该协议比Paxos开销更大。有两种可能的对Generalized Paxos的改进,可以提升恢复时间。

  • 首先,如果协调者(coordinator)是每个接收者仲裁组的一部分(回合N被称为centered),那么在N+1回合恢复在N回合的冲突,协调者跳过阶段1,在阶段2提交它在最后的回合N期间收到的序列。这把恢复的开销减少为单个回合过程。
    译者注:即不需要leader来选择序列,而是由协调者(它也是接收者)来发一个Accepted消息来确定序列,以此一个消息来解决冲突并确定序列。
  • 其次,如果回合N和N+1都使用了一个唯一的完全相同的centered仲裁组,当一个接收者在回合N检测到冲突,它自发地在回合N+1提交这样的序列,该序列的后缀是(i)在回合N由协调者接收的序列,以及(ii)它在回合N收到的最大不冲突的前缀。例如,如果协调者和接收者在回合N依次接收到<WriteB, ReadB>和<ReadB, ReadA>,那么该接收者将自发地在回合N+1接收<WriteB, ReadB, ReadA>。有了这个变化,恢复的开销就是单个消息延迟了,这是明显的优化。这里注意,在一个回合中唯一仲裁组的使用不损害活跃度。这是由于这样的事实:在这个仲裁组中的任意处理器是下一个回合的准备(prepare)阶段的一个read仲裁组(quorum) – 可以理解为这样的仲裁组在下个回合中是只读属性的,不会去修改这个仲裁组。

拜占庭Paxos

axos也可以扩展来支持任意参与者的故障,包括消息的欺骗和虚构,与其他参与者的串通,选择非参与者等等。在Lamport推广解决方案之后,这些故障类型被称为拜占庭故障(Byzantine failures)。

拜占庭Paxos(Byzantine Paxos)是由Castro引入的,Liskov添加了一个额外的消息(Verify),它的作用是分发认知并且验证其他处理器的动作。

消息流:拜占庭Multi-Paxos,稳定的状态

在这里插入图片描述
Fast拜占庭Paxos由Martin引入,Alvisi移除了这个额外的延迟(Verify),这样客户端直接将命令发送给接收者。

注意在Fast拜占庭Paxos中的Accepted消息被发送给所有接收者和所有学习者,而Fast Paxos只会把Accepted消息发给学习者,如下:

消息流:快速拜占庭Multi-Paxos,稳定的状态

在这里插入图片描述
对于两个协议来说故障场景是相同的。每个学习者等待接收来自不同接收者的F+1个完全相同的消息。如果这没有发生,接收者它们自己也将会知道(因为它们在广播回合中交换彼此的消息),并且正确的接收者将重新广播协议值:

消息流:快速拜占庭Multi-Paxos,故障

在这里插入图片描述

使Paxos适配RDMA网络

随着支持RDMA(远程DMA)的高速可靠的数据中心网络的出现,人们对优化Paxos来利用硬件分流产生了浓厚的兴趣,其中网络接口卡和网络路由提供可靠性和网络层拥塞控制,释放主机CPU来执行其他任务。Derecho C++ Paxos库是一个开源的探索这个选择的Paxos实现。

Derecho为内存中复制( in-memory replication)和状态机( state-machine synchronization)同步提供了一个经典Paxos(在整个关闭/重启序列中具有数据持久性)和垂直Paxos(原子多播)。Derecho使用的Paxos协议需要调整来适配最大异步数据流,在领导者的关键路径上移除其他延迟的资源。这样做使得Derecho可以支持全双工RDMA数据速率。另外,尽管传统的Paxos协议可以通过简单地把消息发送操作映射到本地RDMA操作,以此来迁移到一个RDMA网络,这样做保留了在关键路径上的回合过程延迟。在高速RDMA网络中,即使小的延迟也可能足够大来阻断对完整潜在带宽的利用。

使用Paxos的产品

  • Google在他们的Chubby分布式锁服务中使用了Paxos算法,以维持在故障时副本的一致性。Chubby有Bigtable使用,它现在在Google分析学和其他产品中已产品化了。
  • Google Spanner and Megastore内部使用Paxos算法。
  • OpenReplica replication service使用Paxos来为一个开放访问系统维护副本,这个系统使得用户可以创建容错的对象。它通过并发的回合提供高性能,以及通过动态改变成员关系来提供灵活性。
  • IBM据说在他们的IBM SAN Volume Controller产品中使用了Paxos算法,以实现常规的虚拟机容错目的,虚拟机是用来运行配置和控制集群提供的存储虚拟化服务的组件。
  • Microsoft在来自北京的Autopilot cluster management service中使用Paxos,以及在Windows服务器故障恢复集群中。
  • WANdisco已经在他们的DConE active-active 复制技术中实现了Paxos。
  • XtreemFS使用了一个基于Paxos的租约协商算法来容错和文件和元数据的一致性复制。
  • Heroku使用了实现了Paxos来做一致性分布式数据存储的Doozerd。
  • Ceph使用Paxos作为监控器进程的一部分,一对哪个OSD是开启的以及是在集群中的达成一致意见。
  • Clustrix为分布式事务问题使用Paxos来分布SQL数据库。
  • 从v1.9开始,Neo4j HA图像数据库实现Paxos,替换Apache Zookeeper。
  • Apache Cassandra NoSQL数据库只为轻量事务特性而使用Paxos。
  • Amazon ECS使用Paxos来维护集群状态的一致性视图。

参考

Chandra–Toueg consensus algorithm
State machine
Raft

引用

扩展连接

Leslie Lamport’s home page
Paxos Made Simple
Revisiting the Paxos Algorithm
Paxos Commit
Google Whitepaper: Chubby Distributed Lock Service
Google Whitepaper: Bigtable A Distributed Storage System for Structured Data
Survey of Paxos Algorithms (2007)
OpenReplica Open Replication Service
FTFile: Fault Tolerant File library
Isis2 library (the SafeSend primitive is a free, open source implementation of Paxos)
Mencius - Circular rotating Paxos for geo-distributed systems
WANdisco - Active-Active Replication solutions for Hadoop, Subversion & GIT
libpaxos, a collection of open source implementations of the Paxos algorithm
libpaxos-cpp, a C++ implementation of the paxos distributed consensus algorithm
JBP - Java Byzantine Paxos
erlpaxos, Paxos by Erlang
paxos - Straight-forward paxos implementation in Python & Java
Manhattan Paxos (mpaxos), Paxos in C, supporting multiple paxos groups and efficient transactions across them.
Clustering with Neo4j
HT-Paxos
PaxosStore, paxos implementation in WeChat
LWT in Cassandra

写在最后

  • 文章大概完成于2018年,一直躺在电脑的某个角落。最近想回顾过去的十年,重新翻了出来。
  • 本文翻译自Wiki中Paxos页面,点击查看原文。不知道这几年过去了,页面内容是否还跟本文中的翻译能对上。
  • 对不上也没关系,看本文足够对Paxos有个初步了解了。
  • 文中对句子和词汇的翻译都是直译,没有经过语文优化的,读起来拗口时,还请海涵。待我有空的时候再去优化一下。
;