摘要
当以浅显的英语呈现,Paxos算法非常简单。
1. 引言
用于实现容错分布式系统的Paxos算法被认为难以理解,可能是因为对于许多读者来说对其初始介绍是希腊语[5]。实际上,它是最简单和最明显的分布式算法之一。其核心是一致性算法 - [5]中的“synod”算法。下一节表明,这种一致性算法几乎不可避免地来自我们希望它满足的特性。最后一节解释了完整的Paxos算法,该算法是通过直接应用一致性建立分布式系统的状态机方法获得的 - 这种方法应该是众所周知的,因为它是关于分布式系统理论文章[4]中最常被引用的主题。
2. 一致性算法
2.1 存在的问题
假设有一个可以提出值的进程集合。一致性算法确保只选择所提出的值中的某单一值。如果没有提出任何值,则不应选择任何值。如果已选择某个值,则进程应该能够学习所选值。达成一致性的安全要求是:
- 只能选择已提出的值,
- 只选择一个值,并且
- 除非实际已经选择,否则进程永远不会知道已选择了某个值。
我们不会尝试指定明确的活跃度要求。但是,目标是确保最终选择某一提出值,如果选择了一个值,那么一个进程最终可以获得该值。
我们让一致性算法中的三个角色由三类代理履行:提议者、接受者和学习者。在一个实现中,单个进程可以充当多个代理,但是代理到进程的映射在这里并不引起我们关注。
假设代理可以通过发送消息来相互通信。我们使用惯常的异步非拜占庭模型,其中:
- 代理以任意速度运行,可能因停止而失败,并可能重新启动。由于所有代理在选择值后可能会失败,然后重新启动,因此除非某些信息可以由失败并重新启动的代理记住,否则无法解决问题。
- 消息可能需要很长时间才能传递,可能会重复,也可能会丢失,但它们不会被破坏。
2.2 选择一个值
选择值的最简单方法是使用单个接受者代理。提议者向接受者发送提议,接受者选择其收到的第一个提议值。虽然简单,但这种解决方案并不令人满意,因为接受者的失败使得任何进一步的进展都变得不可能。
那么,让我们尝试另一种选择值的方法。我们使用多个接受者代理而不是单个接受者。提议者将提议值发送给一组接受者。接受者可以接受提议的值。当足够多的接受者接受它时,则选择该值。多少是足够多?为了确保只选择一个值,我们可以让一个足够大的集合由任意大多数代理组成。因为任何两个大多数都有至少一个共同的接受者,如果接受者最多可以接受一个值,则这是有效的。(在许多论文中都观察到了大多数的明显概括,显然从[3]开始。)
在没有失败或消息丢失的情况下,即使只有单个提议者提议的一个值,我们也希望选择一个值。这意味着要求:
P1. 接受者必须接受其收到的第一个提议。
但是这个要求引发了一个问题。不同的提议者可以在几乎同时提出多个值,导致每个接受者都接受了一个值的情形,但没有单个值被大多数接受者接受。即使仅提出两个提议值,如果每个值被约一半的接收者接受,那么单个接受者的失败可能使得无法了解已经选择了哪个值。
P1和仅在大多数接受者接受时选择值的要求意味着必须允许接受者接受多个提议。我们通过为每个提议分配一个(自然)编号来跟踪接受者可能接受的不同提议,因此提议包含提议编号和值。为防止混淆,我们要求不同的提议具有不同的编号。如何做到这取决于实现,所以现在我们只是假定它。当具有该值的单个提议已被大多数接受者接受时,将选择这个值。在这种情况下,我们说已经选择了提议(及其值)。
我们可以允许选择多个提议,但我们必须保证所有选择的提议具有相同的值。通过对提议编号的归纳,足以保证:
P2. 如果选择了具有值v的提议,则所选择的每个更高编号的提议都具有值v
由于编号是完全有序的,因此条件P2保证了仅选择单个值的关键安全特性。
要被选择,必须至少有一个接受者接受提议。因此,我们可以通过满足以下条件来满足P2:
P2a. 如果选择了具有值v的提议,则任何接受者接受的每个更高编号的提议都具有值v。
我们仍然保留P1以确保选择某一提议。由于通信是异步的,因此可以选择一个提议,即使其中某个特定的接受者c从未接收过任何提议。假设一个新的提议者“醒来”并发出一个具有不同值的更高编号的提议。P1要求c接受此提议,这违反了P2a。同时保持P1和P2a需要加强P2a:
P2b. 如果选择了具有值v的提议,则由任何提议者发布的每个更高编号的提议具有值v。
由于提议必须在提议可以被接受者接受之前由提议者发布,P2b意味着P2a,而P2a又意味着P2。
为了发现如何满足P2b,让我们考虑如何证明它是成立的。我们假设选择了一个编号为m和值为v的提议,并表明任何以编号n>m发出的提议也具有值v。我们可以通过在n上使用归纳来使证明更容易,因此我们可以证明提议编号n具有值v的附加假设是每个以m…(n-1)中编号发出的提议具有值v,其中i…j表示从i到j的数字集合。对于选择的编号为m的提议,必须有一些由大多数接受者组成的集合C,使得C中的每个接受者都接受它。将此与归纳假设相结合,选择m的假设意味着:
C中的每个接受者都接受了一个编号在m..(n-1)范围中的提议,并且任何接受者接受的编号为m..(n-1)的每个提议都具有值v。
由于任何由大多数接受者组成的集合S至少包含C中的一个成员,因此我们可以得出结论,通过确保维护以下不变量,编号为n的提议具有值v:
P2C. 对于任何v和n,如果发布了具有值v及编号n的提议,则存在由大多数接受者组成的集合S,使得(a) S中的接受者没有接受任何编号小于n的提议,或者(b) v是所有提议(S中的接受者所接受的提议的编号小于n)中编号最高的提议的值。
因此,我们可以通过保持P2c的不变性来满足P2b。
为了保持P2c的不变性,想要发布编号为n的提议的提议者必须获得编号最大的提议(其编号小于n,如果有的话,已经或将被某些大多数接受者中的每个接受者接受)。了解已经接受的提议很容易;预测未来的提议接受很难。提议者不是试图预测未来,而是通过提取不会有任何此类提议接受的承诺来控制它。换句话说,提议者要求接受者不再接受任何编号小于n的提议。这导致以下用于发布提议的算法。
-
提议者选择新的提议编号n并向某些接受者集合中的每个成员发送请求,要求其回复:
- 承诺永远不再接受编号小于n的提议,和
- 有最高编号的提议(小于其已接受的n,如果有的话)。
我将这样的请求称为编号为n的准备请求。
-
如果提议者从大多数接受者收到请求的响应,则它可以发出具有编号n和值v的提议,其中v是响应中编号最高的提议的值,或者是提议者选择的任何值,如果响应者报告没有提议。
提议者通过向一组接受者发送提议已被接受的请求来发布提议。(这不一定是响应初始请求的同一组接受者。)让我们称之为接受请求。
这描述了提议者的算法。接受者是怎么样的?它可以从提议者那里收到两种请求:准备请求和接受请求。一个接受者可以忽略任何请求而不影响安全性。因此,我们只需要说明何时允许响应请求。它始终可以响应准备请求。它可以响应接受请求,接受提议,如果它没有承诺不接受。 换一种说法:
P1a. 如果接受者未响应具有大于编号n的准备请求,则接受者可以接受编号为n的提议。
观察到P1a包含P1。
我们现在有一个完整的算法来选择满足所需安全特性的值 - 假设提议编号唯一。通过进行一次小的优化来获得最终算法。
假设接受者收到编号为n的准备请求,但它已经响应了编号大于n的准备请求,从而承诺不接受任何编号为n的新提议。然后,接受者没有理由响应新的准备请求,因为它不接受提议者想要发布的编号为n的提议。所以我们让接受者忽略这样的准备请求。我们也忽略了对已经接受的提议的准备请求。
通过此优化,接受者只需要记住它已接受的最高编号提议以及它已响应的编号最高的准备请求的编号。由于P2c无论故障与否必须保持不变,接收者必须记住此信息,即使它失败然后重新启动。请注意,提议者可以随时放弃提议并忘记所有提议 - 只要它永远不会尝试使用相同的号码发布另一个提议。
将提议者和接受者的行为放在一起,我们看到算法在以下两个阶段中运行。
-
阶段1
- 提议者选择编号n的提议并向大多数接受者发送编号为n的准备请求。
- 如果接受者收到的编号n的请求的编号大于其已经回复的任何准备请求的编号,则它会响应该请求,并承诺不再接受编号小于n的提议并保存已接受的编号最高的提议(如果有)。
-
阶段2
- 如果提议者从大多数接受者收到对其准备请求(编号为n)的响应,则它向每个接受者发送一个接受请求,以获得编号为n且值为v的提议,其中v是响应中提议编号最高的值,如果响应未报告任何提议,则为任何值。
- 如果接受者收到对编号为n的提议的接受请求,则它接受该提议,除非它已经响应了具有大于编号n的准备请求。
提议者可以制作多个提议,只要它遵循每个提议的算法即可。它可以随时放弃处于协议过程中的提议。(即使在提议被放弃很久之后,提议的请求和/或响应可能才到达目的地,也可以保持正确性。)如果某些提议者已经开始尝试发布更高编号的提议,那么放弃提议可能是一个好主意。因此,如果接受者因为已经收到了更高编号的准备请求而忽略了准备或接受请求,那么它应该通知提议者,然后提议者应该放弃其提议。这是一种性能优化,不会影响正确性。
2.3 获取选择的值
要了解已选择了一个值,学习者必须发现提议已被大多数接受者接受。显而易见的算法是让每个接受者在接受提议时,给所有学习者响应,向他们发送该提议。这允许学习者尽快找出所选择的值,但是它要求每个接受者对每个学习者做出响应 - 响应数量等于接受者数量和学习者数量的乘积。
非拜占庭式失败的假设使得一个学习者很容易从另一个学习者那里发现一个值被接受了。我们可以让接受者以他们对一个杰出学习者的接受来响应,这反过来又会在选择了一个值时通知其他学习者。这种方法需要额外的轮次让所有学习者发现所选择的值。它也不太可靠,因为杰出的学习者可能会失败。但它需要响应的数量仅等于接受者数量和学习者数量的总和。
更一般地,接受者可以用他们对一组杰出学习者的接受来响应,每个杰出学习者可以在选择了一个值时通知所有学习者。使用更多的杰出学习者以更高的通信复杂性为代价提供更高的可靠性。
由于消息丢失,可以选择一个没有学习者发现的值。学习者可以向接受者询问他们接受了哪些提议,但是接受者的失败可能使得无法知道是否多数接受者已接受了某个特定的提议。在这种情况下,学习者只有在选择新提议时才会知道选择了什么值。如果学习者需要知道是否已经选择了某个值,则可以使用上述算法让提议者发出提议。
2.4 推进
很容易构建一个场景,其中两个提议者各自继续发布一系列具有越来越大编号的提议,其中没有一个被选中。提议者p完成提议编号n1的阶段1。 然后,另一个提议者q完成提议编号n2>n1的阶段1。提议者p的阶段2对编号为n1的提议的接受请求被忽略,因为接受者都承诺不接受任何编号小于n2的新提议。因此,提议者p然后开始并完成新的编号n3>n2的提议的阶段1,从而导致提议者q的第二个阶段2的接受请求被忽略。如此往复。
为了保证向前推进,必须选择一位杰出的提议者作为尝试发布提议的唯一提议者。如果杰出的提议者可以与大多数接受者成功通信,并且如果它使用的编号大于任何已使用的提议,那么它将成功发布被接受的提议。如果它获知具有更高提议编号的某些请求,通过放弃提议并再次尝试,则杰出提议者最终将选择足够高的提议编号。
如果系统(提议者,接受者和通信网络)足以正常工作,则可以通过选择一个杰出的提议者来实现活跃性。Fischer,Lynch和Patterson[1]的著名结果意味着选择提议者的可靠算法必须使用随机性或实时性 - 例如,通过使用超时。但是,无论选举的成败与否,都能确保安全。
2.5 实现
Paxos算法[5]假设一个进程网络。在其一致性算法中,每个进程都扮演着提议者、接受者和学习者的角色。该算法选择一个领导者,该领导者扮演杰出提议者和杰出学习者的角色。Paxos一致性算法正是上面描述的算法,其中请求和响应作为普通消息发送。(响应消息标记有相应的提议编号以防止混淆。)在故障期间保留的稳定存储用于维护接受者必须记住的信息。在实际发送响应之前,接受者将其预期响应记录在稳定存储中。
剩下的就是描述保证不会发出两个具有相同编号的提议的机制。不同的提议者从不相交的数字集中选择他们的编号,因此两个不同的提议者从不发出具有相同数字的提议。每个提议者都会记住(在稳定存储中)它尝试发布的编号最高的提议,并以高于其已使用的提议编号开始阶段1。
3. 实现状态机
实现分布式系统的一种简单方法是作为将命令发布到一个中央服务器的客户端集合。可以将服务器描述为以某种顺序执行客户端命令的确定性状态机。状态机具有当前状态;它通过将命令作为输入并产生输出和新状态来执行步骤。例如,分布式银行系统的客户端可能是柜员,状态机状态可能包括所有用户的帐户余额。当且仅当余额大于提取的金额时,执行退出将通过执行降低帐户余额的状态机命令来执行提取,从而产生新旧余额作为输出。
如果该服务器发生故障,则使用单个中央服务器的实现将失败。因此,我们使用一组服务器,每个服务器独立地实现状态机。因为状态机是确定性的,所以如果它们都执行相同的命令序列,则所有服务器将产生相同的状态序列和输出。然后,发出命令的客户端可以使用任何服务器为其生成输出。
为了保证所有服务器执行相同的状态机命令序列,我们实现了Paxos一致性算法的一系列单独实例,第i个实例选择的值是序列中的第i个状态机命令。每个服务器在算法的每个实例中扮演所有角色(提议者,接受者和学习者)。目前,我假设服务器集是固定的,因此一致性算法的所有实例都使用相同的代理集。
在正常操作中,单个服务器被选为领导者,其在一致性算法的所有实例中充当杰出提议者(唯一尝试发布提议的提议者)。客户端向领导者发送命令,领导者决定每个命令应该出现在序列中的哪个位置。如果领导者决定某个客户端命令应该是第135个命令,则它尝试将该命令选择为一致性算法的第135个实例的值。它通常会成功。它可能因为失败而失败,或者因为另一台服务器也认为自己是领导者并对第135个命令应该是什么有不同的想法。但是,一致性算法确保最多只可以选择一个命令作为第135个命令。
这个方法有效的关键在于,在Paxos一致性算法中,直到阶段2才会选择要提议的值。回想一下,在完成提议者算法的阶段1之后,要么确定要提议的值,要么提议者可以自由提议任何值。
我现在将描述Paxos状态机实现在正常操作期间的工作原理。稍后,我将讨论可能出现的问题。我考虑当前任领导者刚刚失败并且选择了新的领导者时会发生什么。(系统启动是一种特殊情况,尚未提出任何命令。)
作为一致性算法的所有实例中的学习者,新领导者应该知道已经选择的大多数命令。假设它知道命令1-134,138和139,即在一致性算法的实例1-134,138和139中选择的值。(稍后我们将看到如何在命令序列中出现这样的差距。)然后它执行实例135-137的阶段1和所有大于139的实例。(我在下面描述如何完成这个。)假设这些执行的结果确定了实例135和140中要提出的值,但在所有其他实例中保留了提议值。然后领导者为实例135和140执行阶段2,从而选择命令135和140。
领导者以及学习领导者知道的所有命令的任何其他服务器现在可以执行命令1-135。然而,它也不能执行它也知道的命令138-140,因为还没有选择命令136和137。领导者可以将客户请求的下两个命令作为命令136和137。相反,我们通过提出一个使状态保持不变的特殊“noop”命令作为命令136和137来立即填补空隙。(它通过执行一致性算法的实例136和137的阶段2来实现。)一旦选择了这些无操作命令,就可以执行命令138-140。
现在已经选择了命令1-140。对于一致性算法中所有超过140的实例,领导者也完成了阶段1,并且可以在这些实例的阶段2中自由地提出任何值。它将命令编号141分配给客户端请求的下一个命令,将其提议作为一致性算法的实例141的阶段2中的值。它建议它接收的下一个客户端命令作为命令142,依此类推。
领导者在得知其所提出的命令141已被选择之前可以提出命令142。它在提议命令141中发送的所有消息都可能丢失,并且在任何其他服务器已经知道领导者提出的命令141之前选择命令142。当领导者未能收到对实例141中阶段2消息的预期响应时,它将重新传输这些消息。如果一切顺利,将选择其建议的命令。但是,它可能首先失败,在所选命令的序列中留下间隙。通常,假设领导者可以提前获得a命令 - 也就是说,在选择命令1到i之后,它可以提出命令i+1到i+a。然后可能出现高达a-1个命令的间隙。
对于实例135-137和所有大于139的实例,新选择的领导者为无限多个一致性算法实例(在上述场景下)执行阶段1。对于所有实例使用相同的提议编号,它可以通过对其他服务器发送单个合理短消息来执行此操作。在阶段1中,只有当接收者已经收到来自某个提议者的阶段2消息时,它才会响应不止一个简单的OK。(在该场景中,仅针对实例135和140的情况。)因此,服务器(充当接受者)可以使用单个合理短消息来响应所有实例。因此,执行这些无限多个阶段1的实例不会产生任何问题。
由于领导者的失败以及新领导的选举应该是罕见的事件,执行状态机命令的有效性成本 - 即在命令/值上达成一致性 - 是仅执行一致性算法的阶段2的成本。可以证明,Paxos一致性算法的阶段2具有在存在故障时达成一致的任何算法的最小可能成本[2]。因此,Paxos算法基本上是最优的。
关于系统正常运行的讨论假定总是有一个单一领导者,除了当前领导者的失败和新领导者选举之间的短暂时期。在异常情况下,领导者选举可能会失败。如果没有服务器充当领导者,则不会提出新的命令。如果多个服务器认为它们是领导者,那么他们都可以在一致性算法的相同实例中提出值,这可以防止选择任何值。但是,安全性得以保留 - 两个不同的服务器永远不会对作为第i个状态机命令选择的值产生不同意见。选举一个单一领导者只是为了确保取得进展。
如果服务器集合可以改变,那么必须有某种方法来确定哪些服务器实现了一致性算法的哪些实例。最简单的方法是通过状态机本身。当前的服务器集合可以成为状态的一部分,并且可以使用普通的状态机命令进行改变。我们可以允许领导者提前获得a个命令,通过让服务器集合执行一致算法的实例i+a(由第i个状态机命令执行之后的状态来确定)。这允许简单地实现任意复杂的重新配置算法。
参考文献
- 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.
- 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).
- Leslie Lamport. The implementation of reliable distributed multiprocess systems. Computer Networks, 2:95–114, 1978.
- Leslie Lamport. Time, clocks, and the ordering of events in a distributed system. Communications of the ACM, 21(7):558–565, July 1978.
- Leslie Lamport. The part-time parliament. ACM Transactions on Computer Systems, 16(2):133–169, May 1998.