【翻译】paxos made simple

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了【翻译】paxos made simple相关的知识,希望对你有一定的参考价值。

参考技术A Paxos算法实现了一个容错的分布式系统,它往往被认为是难以理解的,可能因为最早的表述对于大多数读者来说是读希腊故事[5]。

事实上,在所有分布式算法中,Paxos算法是最简单的,也是最显而易见的。

其核心就是一个一致性算法-the “synod” algorithm of [5].

从下面的章节可以看出,这种一致性算法不可避免地满足了我们希望它满足的性质。

最后一节解释了完整的Paxos算法,可以通过构造分布式系统的一个众所周知的方法来得到,即,从一致性到状态机的直接应用。

他也是在分布式理论论文中经常引用的一个主题。

假定一组进程可以提议一些values。有一种一致性算法,它确保在所有被提议的values中,选择(chosen)其中一个。

如果没有value被提议,那么就没有value被选择(chosen)。如果有一个value被选择,那么进程应该可以学习此value。

一致性的安全需求如下:

. 只有被提议(proposed)的value才能被选择(chosen)

. 只有一个value被选择

. 一个进程只能学习确定被选择(chosen)的value

我们并不会尝试指出精确的活性要求。然而,目的是确保一些被提议(proposed)的value最终能被选择(chosen),

如果一个value被选择(chosen),那么一个进程最终能学习到此value。

我们让三类代理,如proposers, acceptors, and learners来扮演分布式算法中的三种角色。

对于某一种具体实现,一个单独的进程可能扮演多种角色。然而,我们这里并不会关心这种角色的具体分工。

假如这些角色代理之间可以通过发送消息来通信。

我们采用通常意义的异步,没有拜占庭问题的通信模型,为此:

. 代理可以任意速度运行,可能由于停止服务而故障,可能会重启。由于所有的代理服务可能在一个value被chosen后故障,然后又重启成功,
所以一个可能的解决方案是代理能对关键信息进行持久化保存。

. 消息分发的时延是任意长的,允许重复,允许丢失,但是消息内容不能发生错误(译者注:非拜占庭问题)。

选择一个value最简单的方法就是只有一个acceptor。某proposer发送一个proposal给acceptor,然后acceptor选择它第一次收到的proposed value。

尽管简单,然而不能满足我们的要求。因为acceptor agent故障会导致后续的工作没法进行。

因此,我们还得尝试别的方法。和单个acceptor比较而言,我们采用多个acceptor。某proposer给一组acceptor发送proposed value。

某些acceptor可能会接受proposed value。如果有足够大的多数派acceptor集合接受了此proposed value,我们认为此value被选择。

多大算是足够大呢?为了确保只有一个value被选择(chosen), 我们让一个足够大的集合由任意多数派组成。

因为任意两个多数派至少会有一个共同的acceptor,所以只要一个acceptor最多只接受一个value,那么这个办法是可行的。

(这种显而易见的多数派,在许多论文中看到过,最明显的起始于[3])

在没有故障或者消息丢失的情况下,及时只有一个proposer提议了一个value,我们要求选择(chosen)此value。

这提出了要求:

P1. acceptor必须接受他收到的第一个提案。

但是这个要求引发了一个问题。不同的proposer可能会同时提议value,这可能会导致一种情况:

尽管每一个acceptor接受了一个value,但是没有一个value被大多数acceptor接受。

甚至在只有两个value被提议的场景下,假如刚好每一半acceptor分别接受了这两个value,

某个acceptor发生故障会导致无法确定哪个value被选择。(译者注:这是要说acceptor的个数是奇数的情况下吗?)

P1和多数派接受才能选择一个value的要求,意味着acceptor必须能接受多个提案。

我们通过给每一个提案编号来跟踪不同的提案,因此一个提案由编号和value组成。

为了防止混淆,我们要求提案不同,编号不同。如何实现此目标依赖具体实现,目前为止,我们仅仅是假设而已。

当多数派acceptor接受了一个提案,提案携带的value被选择。此时,我们说提案被选择。

我们允许选择多个提案,但是我们必须确保这些被选择的提案有相同的vlaue。(译者注:同一个acceptor可以接受提案。但是他们的个value必须相同, 为了满足“安全属性":只有一个值被选择)

通过对提案编号进行归纳,就足以保证:

P2. 如果拥有value v的提案被选择,那么每一个被选择的更大编号的提案也拥有value v。

由于编号是全序的,所以条件P2确保了关键安全属性:只有一个value被选择

一个提案要想被选择,那么至少被一个acceptor接受。因此,我们可以通过满足如下条件来满足P2:

P2a. 如果拥有value v的提案被选择,那么任意acceptor接受的每一个更大编号的提案拥有value v。(译者注:约束acceptor)

为了确保提案能被选择,我们任然需要维护P1. 因为通信是异步的,某个提案可能被一个从来没有接收过任何提案的acceptor c所选择。

假设一个新醒来的proposer发送了一个带有不同value的更大编号的提案。

P1 要求c接收此提案,违反了P2a. 同时维护P1和P2a需要加强P2a到:

P2b. 如果拥有value v的提案被选择,那么任意proposer发送的每一个更大编号的提案拥有value v。(译者注:约束proposer)

由于一个提案被一个acceptor接受之前,它必须由proposer发送,那么由P2b蕴含着P2a,从蕴含着P2.

为了发现如何满足P2b, 我们可以考虑如何来证明P2b是成立的。

我们可以假设编号为m,value是v的提案被选择,能够得出任何被发送的编号满足条件n>m的提案也拥有value v。

我们可以对编号n进行归纳,做一个简单的证明。

因此我们要做的是,假设每一个被发送的编号是m..(n-1)的提案拥有value v(其中i..j表示从i到j的编号集合),能够证明编号为n的提案拥有value v。(此条是归纳假设)

对于编号为m的提案被选择,那么必然存在由多数派acceptor组成的集合C,以至于集合C中的每一个acceptor接受了此提案。

联合词条和归纳假设,假设m被选择,蕴含着:

集合C中的每一个acceptor已经接受了编号m..(n-1)的提案,并且每一个被任意acceptor接受的拥有编号m..(n-1)的提案拥有value v。

由于任何由多数派组成的集合S至少包含了一个集合C中的成员,我们可以通过确保维护下面的不变性得出编号为n的提案拥有value v:

P2c. 对于任意v和n,如果一个拥有value v和编号n的提案被发送,那么存在一个有多数派acceptor组成的集合S,以至于:

(a) 没有集合S中的acceptor曾经接受过任意编号小于n的提案。或者,(b) 由集合S所接受的所有编号小于n提案中,编号最大的提案的value是v。

因此我们可以通过维护P2c的不变性来满足条件P2b.

为了维护P2c的不变性,一个proposer要想发送一个编号为n的提案,那么对于任意已经或者将要被多数派接受的提案,他必须学习提案编号比n小的最大编号。

学习已经接受的提案是很简单的,预测未来接受的时候困难的。我们并不是要尝试预测未来,proposer通过获得将来不会再有这样接受的承诺,去控制“学习提案编号比n小的最大编号”。

换句话说,proposer请求acceptors不在接受任意编号小于n的提案。这会得出如下的算法,用于发送提案:

(a) 承诺不再会接受编号小于n的提案,或者,

(b) 它(acceptor)接受的所有提案的编号小于n。

我把这样的请求称做编号为n的prepare request。

或者是如果应答人没有报告提案,proposer可以选择任意value。

proposer发送提案请求给一组acceptor。(这组acceptor和接收prepare request的acceptors可以不是一组)

我们把这个请求叫做accept request。

上面描述了proposer算法。acceptor的算法是什么呢?acceptor可以从proposer接受到两类请求: prepare requests and accept requests。

acceptor可以忽略任意请求,不会破坏安全性。因此,我们应该说只有当acceptor被允许应答proposer的请求时,acceptor才会应答。

acceptor可以总是响应prepare request。它也可以接受一个提案,去响应accept request,如果他没有承诺不这样做的话。换句话说:

P1a. 如果acceptor曾经没有对编号大于n的prepare request做出响应,那么它可以接受一个编号为n的提案。

通过观察可以发现,P1a预示着P1。

现在我们有完整的算法来选择满足安全属性的value。最终的算法需要做少许优化:

假定一个acceptor接收了编号为n的prepare request,但是他已经对一个编号大于n的prepare request做出了应答,

所以它得承诺不能再接受任意编号为n的新的提案。那样,acceptor再没有理由去响应新的prepare request。

至此,他将不会再接受proposer想要发送,但是编号大于n的提案。

因此,我们将会使acceptor忽略这样的prepare request。我们当然也会让acceptor忽略已经接受的prepare request。

有了这样的优化,acceptor只需要记录他曾经接受的编号最大的提案和他曾经响应的编号最大的prepare request的number。

因为不管什么故障P2c必须要保持不变性,acceptor必须记录这些信息,及时它故障又重启。

需要注意的是,proposer可以总是丢弃某个提案,并且只要他不在发送相同编号的提案,他可以忘记和此提案相关的所有信息。

把proposer和acceptor的行为集中在一起,我们可以发现算法的执行分两个阶段:

Phase 1. (a) proposer选择一个提案编号n,发送编号为n的prepare request给多数派acceptor。

Phase 2. (a) 如果proposer收到了来自多数派acceptor对他prepare requests(编号n)的应答,那么,

只要遵守算法规则,一个proposer可以产生很多提案。他可以在协议过程的任何时间点丢弃某个提案。(尽管当提案被丢弃以后,此提案的请求和/或者响应可能才到达目的地,

然而正确性是能够保证的。)如果有proposer尝试发送更大编号的提案,那么选择丢弃一个提案很可能是很好的想法。

因此,假如一个acceptor由于接受了更大编号的prepare request,而忽略一个prepare or accept request,那么它应该通知想丢弃此提案的propser。

这是一个性能优化,并不会影响正确性。

2.3 Learning a Chosen Value(学习被选择的值)

为了学习一个选定的值,learner必须找出被多数派接受的提案。显而易见的算法是,acceptor无论在什么时候接受了一个提案,都应该发送给learner。

这允许learners尽可能的找出被选择的value,这要求每一个acceptor响应每一个learner。响应的数量等于acceptors个数和learners个数的乘积。

非拜占庭故障的假设,使得某个learner从另一个learner找出被接受的value变得很简单。

我们可以让所有acceptors把他们接受的提案通知给不同的learner,每一个learner在通知其他learner。

要想让所有learner发现被选择的value,这种方法额外需要一轮。这也不太可靠,因为learner可能会故障。

但是,响应的数量等于acceptors个数和learners个数的和。

更通用的,acceptors可以将接收的提案发送给不同learners集合,每一个learner再将被选择的value通知给其他所有learner。

如果不同learner的集合越大,那么相应的通信复杂度月更大,带来的服务更可靠。

由于消息会丢失,被选择的value,没有任何learner知道。learner可以询问acceptors所接受的提案,

但是由于acceptor故障,使得他不可能知道是否某个提案是被多数派接受的。在那种情况下,learner只需要在新的提案被选择的时候,去找出被选择的value。

如果learner需要知道是否一个value被选择,他可以让proposer重新发送一个提案。

2.4 Progress(进度)

可以很容易的构造一个场景,两个proposers发送序号递增的提案,但是没有一个提案会被选择。

Proposer p对提案号n1完成了phase 1。另一个proposer q对提案号n2 > n1完成了phase1。

Proposer P在phase 2的编号为n1的提案的accept request被忽略了,因为acceptors已经做出了

不再接受编号小于n2的任何新的提案。因此,proposer p用新的编号n3>n2来完成phase 1,

这会导致proposer q在phase 2的 accept requests被忽略。然后,重复此过程。

为了确保进度,必须选择一个知名的proposer作为发送提案的唯一一个人。如果知名proposer可以和多数派acceptors正常通信,

并且如果他用了一个提案,其编号大于任何已经用的,那么发送一个想要被接受的提案会成功。

如果得知一些请求带有更大的提案号,可以放弃此提案,并且再次尝试,知名proposer最终会找到足够大的提案编号。

如果系统中大多数角色(proposer, acceptors, and communication network)运行的很好,

可以通过选举一个知名proposer完成活性。Fischer, Lynch, and Patterson的知名结论,

预示着,选举一个proposer的可靠性算法要么使用随机性,要么使用实时时间。例如,可以用timeouts。

然而,无论选举时成功还是失败,安全性是可以保证的。

2.5 The Implementation(实现)

Paxos算法假设了网络中的一组进程。在它的一致性算法中,每一个进程扮演了proposer, acceptor, and learner角色。

算法会选举一个leader,扮演了知名proposer和知名learner的角色。Paxos一致性算法就是前面描述的,请求和响应是以普通的消息来发送的。

(防止迷惑,响应消息带了相应的提案号)。在故障阶段,稳定存储用以维护acceptor必须记录的信息。

在发送响应前,An acceptor需要将响应消息记录在存储中。

接下来会描述一种确保没有两个提案拥有相同编号的机制。不同的proposer从不相交的编号集合中选择自己的编号,

因此两个不同的proposer永远不会发送编号相同的提案。每一个propser需要在存储记录想要发送的编号最大的提案,

开始phase 1,选取的提案号要大于曾经使用过的提案号中的最大值。

为了确保所有服务执行相同的状态机命令序列,我们实现了一系列单独的Paxos一致性算法实例,

由序列中第i个实例选择的value将是第i个状态机命令。在算法的实例中,每一个服务扮演了所有的角色(proposer,acceptor, and learner)。

到目前为止,我假定了服务器的集合是固定的,因此,一致性算法的所有实例使用了相同的代理集合。

在正常操作中,某个server被选为leader。leader在一致性算法的所有实例中扮演了知名的proposer(唯一可以发送提案的那个人)。

客户端把命令发给leader,由leader决定此命令应该处于命令序列中的哪个位置。

如果leader认为某个客户端命令应该是第135个命令,那么leader会试图让一致性算法中第135个实例被选择,此客户端命令作为实例的value被选择。

大多数情况会成功的。不过,有时会因为故障,或者是由于别的服务认为自己是leader,导致了到底谁是第135个命令有了不同的答案。

然而一致性算法可以确保只有一个命令被选择为第135个命令。

此方法的性能关键是,直到phase 2,被提议的value才能被选择。回忆一下,

当proposer的算法phase 1完成后,要么要被提议的value已经被选择,要么proposer可以随意确定任意value。

现在我会描述Paxos状态机在正常操作期间的实现原理。随后,我会讨论可能会发生的错误。

当之前的leader已经故障并且新的leader被选举,我会考虑期间发生什么。

(系统启动阶段是一个特殊情况,期间没有任何命令被提议)

新leader就是一个learner,需要最大可能知道已经被选择的命令。

假定他知道命令1-134,138,和139,意味着此一致性算法的实例1-134,138,139中的value被选择。(随后,我们将会发现命令序列中的空隙是如何产生的)。

紧接着,会执行135-137和所有大于139的实例的phase 1。(下面,我会描述这是如何做的)。

假如这些执行结果决定了在实例135和140中提议的value,但是保留其他实例中被提议的value不受限制。

leader接着会执行实例135和140的phase 2,因此选择命令135和140。

leader,以及学习了leader所知道的所有命令的其他服务,可以执行命令1-135。

然而,尽管他是知道命令138-140,但不能执行,因为命令136和137已经被选择。

leader执行的下两条客户端发送的命令将是命令136和137.紧接着,我们通过马上提议命令136和137,来填充间隙。

一个特殊的"noop"命令保证状态没有改变。(完成这个,通过执行实例136和137的phase 2)。

一旦这些no-op命令被选择,命令138-140可以执行了。

命令1-140已经被选择。leader完成了所有大于140实例的一致性算法的phase 1,在这些实例的phase 2,可以随意提议任意value。

它会把命令号141赋值给客户端请求的下一个命令,提议此命令作为实例141在一致性算法的Phase 2的value。

紧接着,把下一条客户端命令作为命令142,不断重复这个过程。

leader可以在学习被选择的命令141之前,提议命令142。leader很有可能已经提议了命令141,

而其他服务还没来得及学习,此时很有可能提议的命令141中发送的消息被丢失,而命令142却被选择。

当leader没有收到实例141在phase 2的预期的响应,他会重传这些消息。

如果一切很顺利,他提议的命令会被选择。然而,很有可能首先失败了,在被选择的命令中留下一个间隙。

一般情况下,假设领导者预先可以得到α命令,也就是说,可以在命令1到i被选择之后提议命令i + 1到i +α。 那么可能出现高达α-1个命令间隙。

在上面的场景中,新选择的领导者为一致性算法的无限多个实例执行phase 1,有实例135-137和所有大于139的实例。

对所有实例使用相同的提案号码,可以通过向其他服务器发送一条合理简短的消息来实现。

一个acceptor只有在接收了来自一些proposer的phase 2消息,他才不会对phase 1的消息响应一个简单的OK。

(这种场景下,这种情形对应的是实例135和140)。

因此,一个服务(扮演了acceptor) 可以给所有的实例回应一个合理的短消息。

执行无限多个实例的phase 1也不会有什么问题。

因为leader发生故障,而重新选择新leader是小概率事件,执行状态机命令的有效成本,也就是获取command/value的一致性,

是仅仅执行一致性算法phase 2的成本。可以看出,当故障发生时,任何想要达到一致性的算法中,Paxos的代价是最小的。

因此,Paxos算法基本上是最优的。

关于系统正常运行的讨论,假定了总是有一个leader存在,除非是在当前leader故障,

而新leader选举期间,会出现无leader的情况。在异常情况下,leader选举会失败。

如果没有服务扮演leader,那么没有命令会被提议。如果有多个服务都是leader,那么他们都会在一致性算法的同一个实例中提议value,

这会导致任何value都不会被选择。然而,安全是能保证的,即,两个不同的服务器永远不会对第i个状态机命令选择的值产生不同意见。

选举一个单独的leader仅仅是为了确保进度。

如果服务器集合可以改变,那么肯定有办法决定哪些服务实现一致性算法的哪些实例。

实现这个目的最简单的方法就是通过状态机本身。当前的一组服务器可以作为状态的一部分,并可以使用普通的状态机命令进行更改。

[1] Michael J. Fischer, Nancy Lynch, and Michael S. Paterson. Impossibility
of distributed consensus with one faulty process. Journal of the ACM,32(2):374–382, April 1985.

[2] Idit Keidar and Sergio Rajsbaum. On the cost of fault-tolerant consensus
when there are no faults—a tutorial. TechnicalReport MIT-LCS-TR-821,
Laboratory for Computer Science, Massachusetts Institute Technology,
Cambridge, MA, 02139, May 2001. also published in SIGACT News
32(2) (June 2001).

[3] Leslie Lamport. The implementation of reliable distributed multiprocess
systems. Computer Networks, 2:95–114, 1978.

[4] Leslie Lamport. Time, clocks, and the ordering of events in a distributed
system. Communications of the ACM, 21(7):558–565, July 1978.

[5] Leslie Lamport. The part-time parliament. ACM Transactions on Computer
Systems, 16(2):133–169, May 1998.

《Paxos Made Simple》翻译

1 Introduction

可能是因为之前的描述对大多数读者来说太过Greek了,Paxos作为一种实现容错的分布式系统的算法被认为是难以理解的。但事实上,它可能是最简单,最显而易见的分布式算法了。它的本质其实就是共识算法——the "synod" algorithm of。在下一节中我们将展示,该共识算法基本满足了所有我们想要它满足的特性。最后一节则展示了完整的Paxos算法,通过直接应用协商一致的状态虚拟机来构建分布式系统——这种方法应该是广为人知的,因为这可能是分布式系统理论中被引用最多的领域。

2 The Consensus Algorithm

2.1 The Problem

假设有一些进程可以提出value。共识算法保证了在所有提出的value里只有一个会被选中。如果没有value被提出,那么也就没有value会被选中。如果一个value被选中了,那么其他的进程应该能够获取该value。协商一致的要求如下:

  • 只能选择已经被提出的value
  • 只能选择一个value
  • 进程只能获取那些真正被选中的value

我们不会尝试指定精确的要求。但是我们的目标是要确保总有一些被提出的value会被选中,如果一个value最终被选中了,那么其他进程最终要能够获取该value。

我们用三类agent来代表共识算法中的三类角色:proposers, acceptors和learners。在具体的实现中,一个进程可能扮演不止一类agent,但是从agent到进程的映射我们在这里并不关心。

假设agent之间可以通过发送message互相通信。我们使用customary asychronous,non-Byzantine model,其中:

  • Agents以任意速度执行,可能发生故障,可能重启。因为所有的agent都可能在一个value被选中之后故障并重启,因此一般的方法是不可行的,除非agent能记住一些信息,即使发生了故障或重启。
  • 发送的message可以是任意长度的,可能重复,也可能丢失,但是它们不会被损坏

2.2 Choosing a Value

存在一个单一的acceptor agent是最简单的选择value的方式。proposer向acceptor发送提议,后者从中接收最早收到的那个。虽然很简单,但是这种方法是不能满足要求的因为acceptor的故障,因为acceptor的故障就将导致接下来的操作都无法进行。

因此我们需要尝试另一种选择值的方式。这次我们将有多个而不是一个acceptors。proposer将会向一个acceptor的集合发送value。acceptor可能会接受value。但是该value只有在足够多的acceptor都接受它的情况下才算被选择了。那么怎样才算足够大呢?为了确保只有一个value会被选中,我们可以认为一个足够大的agent集合由任意的agent majority组成。因为任意两个majority都至少有一个公共的agent,因此如果一个agent最多只能接收一个value,那么这种方法是可行的。

在没有故障和message丢失的情况下,我们想要有一个value能被选中,即使只有一个proposer提出了一个value。这就需要满足以下要求:

P1. acceptor必须接收第一个它收到的proposal

但是这个要求会引起这样一个问题。不同的proposer可能会在几乎同时提出好几个不同的value,这会导致这样一种情况:每个acceptor都接受了一个value,但是没有一个value是被一个majority接受的。即使只提出了两个value,而它们各自被一半的acceptor所接收,那么任意单个acceptor的故障都将让我们无法获取它选择了哪个value。

P1以及value只有被majority个acceptor接受才被算被选中的要求就导致了我们的acceptor必须能接受超过多于一个的proposal。我们通过给每个proposal赋予一个编号来追踪不同的proposal,所以一个proposal由一个proposal number和一个value组成。为了防止出现歧义,我们要求不同的proposal要有不同的number。这里我们仅仅只是做出这个假设,具体的实现可能有所不同。当一个proposal被一个acceptor的majority所接收时,我们就认为该value被选中了。这种情况下,我们说这个proposal(同时也包括它的value)被选中了。

我们可以允许多个proposal被选中,但是我们必须被选中的proposal必须有相同的value。

以上是关于【翻译】paxos made simple的主要内容,如果未能解决你的问题,请参考以下文章

make-made-made的区别

made his acquaintance|adequate|advisable|announce|contrived to|made up|toss|considering that

sqlchemy self made

Paxos Made Simple(译)

Paxos Made Simple(译)

[Coding Made Simple] Box Stacking