Paxos前传-The Part-Time Parliament(一)

896 阅读11分钟
原文链接: zhuanlan.zhihu.com

前言

之前研究了诸如Raft、ZAB等算法,总觉得意犹未尽,因为只知其然而不知其何以然是一件非常非常不舒服的事情。

本文是对最早提出分布式一致性问题(虽然作者在论文中描述的好像是一个考古学问题)的Lamport的论文“The Part-Time Parliament”的读书笔记,大部分内容是对论文的直接翻译,同时也对一些较为晦涩的地方增加自己的理解注释,可能会存在理解不精确的地方,希望大家多为斧正, 另外本文很多地方直接借用了中文的翻译版,表示感谢,除了个别的笔误,原始的中文翻译版做的很好。

一致性问题本质上就是一群实体对某一个结论达成共识的问题,其核心在于如何设计一种算法(流程)来保证,同时还要能够从数学上严谨地证明算法正确。Lamport在论文中展示了其幽默的一面,虚构了一个古希腊小岛Paxon上的议会机构,议会的主要工作是确定律法,议会由众多议员组成,一个律法的通过必须由多数议员投票方可。作者以此场景为出发点,通过诙谐轻松的语言和严谨的数学推导证明,得出了一个极其简单的算法Paxos,该算法也为当前的大规模分布式系统设计奠定了理论基础。

因为论文涉及到很多数学符号定义,在阅读过程中可能会出现呕吐、头晕、眼花、健忘等不适,忍忍就好。

场景设计

公元 10 世纪初,爱琴海上的小岛Paxos是一个兴盛的商业贸易中心。经济发展带来了政治的进步。Paxos 的公民们用议会形式的政府取代了原先的神权统治。议会的主要作用在于确定律法以保证城市可以有序稳定运转。所有律法都必须经由议会成员投票表决后方可生效实施,且已通过的律法必须被记录在案。但是对 Paxos人来说,做生意才是头等大事,城市职责反在其次(看来这个岛上的居民还比较单纯,不知道玩好政治就等于掌握经济命脉这个道理),没有Paxos人愿意把他全部的时间投入到议会事务中。所以 Paxos 的议会必须在每个议员都可能随时缺席的情况下也能工作下去,这就是所谓的“兼职议会”。

兼职议会所面对的问题正好能对应于今天的容错式分布式系统所面对的问题:议员对应于分布式系统中的处理进程(processes),而议员的缺席对应于处理进程的宕机。因此 Paxos 人的解决方法或许值得计算机科学借鉴(Lamport太谦虚了)。

要求

议会的主要任务就是决定这片土地上的法律,法律是由议会通过的一系列法令定义的。一个现代的议会可以雇佣秘书来记录它的每一个活动,但是在Paxos,没有一个人愿意始终呆在议会大厅里作为一个秘书从头到尾参与每一个会议。取而代之的是每一个议员都会维护一个律簿,用来记录一系列已通过的法令,每个法令带有一个唯一编号。例如议员A的律簿里有这样一个条目:

155:橄榄税是每吨 3 个银币

如果她相信议会通过的第155号法令设置了橄榄的赋税为每吨 3 个银币。律簿是由檫不掉的墨水书写的,所以其中的条目不能被改变。

议会协议的第一个需求就是律簿的一致性。也就是任何两个律簿都不能有互相矛盾的内容。假设议员B在他的律簿中有这样一个条目:

132:只有橄榄油可以用来做灯

那么就不会有其他议员的律簿会记录不同内容的第 132 号法令。当然,另一个议员的律簿里可能还没有第 132 号法令的记录,如果他还不知道第 132 号法令已经通过了。

仅仅有律簿的一致性(Consistency)还不够,因为让每个律簿都保留空白也能满足一致性。所以需要一些要求(Requirement)来保证法令能最终通过并被记录在律簿中。在现代的议会中,议员达不成一致妨碍了法令的通过。但是在 Paxos 不是这种状况,这里盛行的是相互信任的气氛,议员愿意通过任何被提交的法令。但是他们好游历的特性却造成了问题。假如一组议员通过了 37 号法令:

37:禁止在圣殿的墙上种植

然后离开议会厅参加宴会去了,接下来另外一组议员进来议会厅,不知道刚刚发生了什么事情,然后也通过了一个冲突的 37 号法令:

37:允许自由的艺术表达

那么一致性就失去了除非足够多的议员在议会厅里呆足够长的时间,否则进展性(Progress)无法保证。因为 Paxos的议员不愿意缩减他们在外面的活动,所以无法保证任何法令都会最终被通过。但是无论如何,议员们愿意保证,只要他们在议会厅中,他们和他们的助手就会快速的处理所有的议会事务。这个保证使 Paxos 公民们能够设计出一个满足如下进展性条件的议会协议:

如果议员中的多数都在议会厅中,并且在一个足够长的时间内没有人进出议会厅,那么任一被某个议员提议的法令都将会被通过,并且每一个被通过的法令都会出现在议会厅中每个议员的律簿上

假设

通过提供给议员必要的资源,议会协议的要求(requirements)是可以达到的。每个议员收到了一个结实耐用的律簿来记录法令,一支笔,和擦不掉墨水。议员如果离开过议会厅,可能会在回来后忘记了他们曾经做过什么((比如)在一个悲剧的事故里,议员 Twvey 被掉下来的雕像击中了头部而永久失忆了),所以他们会把一些重要的议会任务记在律簿的背面。律簿上的法令条目永远不会改变,但是律簿背面的备注(notes)可能会被划掉。

议员任何时候都会带着他们的律簿,并且总是能够从律簿上阅读到法令条目和尚未划掉的备注。律簿由最精良的羊皮纸做成,只用来记录最重要的备注(notes)。其他备注会被记录到小纸条(a slip of paper)上,这些小纸条可能会在议员离开议会厅后丢失。议会厅里比较嘈杂,影响听觉,不可能在里面做演讲。议员只能通过信使来通信,并且有专款来供议员雇用任意多他们需要的信使。信使不会篡改消息,但是他可能会忘记他递送过了

某个消息,并再次递送它。像他们服务的议员一样,信使也只花他们部分的时间在议会职责上。一个信使在投递一个消息前可能会离开议会厅去从事其他的事情,比如一次为期6个月的航海。他甚至可能会一去不复返,在这种情况下消息永远也不会被送达。

尽管议员和信使可以随时离开或进入,但是只要他们在议会厅里,他们就会专注于议会事务。只要呆在议会厅,信使会很快速的投递消息,议员会立即快速的处理任何他们收到的消息。Paxos 的官方记录声称议员和信使绝对的诚实并严格遵循议会协议。大多数学者仅仅把这当做将 Paxos 描绘为在道德上优越于其东方邻国的宣传。不诚实,尽管稀少,但毫无疑问一定是存在的,但由于官方文本里从未提及,我们也不知道议会是怎样应付不诚实的议员或信使的。

定义说明

Paxos议会采用投票表决的方式来通过法令,这也是民主国家的做法。一个法令只有被特定集合的议员投赞成票才算通过,通过后该法令便被记录在议员的律簿之上。根据现代民主,一般这个特定集合是多数派成员。每一个议员均可以发起提案,每一个提案可能无法在一轮投票中通过,于是该投票可能需要经过多轮投票。为此,我们定义如下几个符号,便于后续推导:

B :表示一轮表决
B_{bal} :表决编号,每一轮表决编号均唯一
B_{dec} :被表决的法令
B_{qrm} :表决的法定人数集(一般指多数派)
B_{vot} :表决的投赞成票的议员集(赞成的投票,不赞成的不投票)
\beta :所有表决集合

一轮表决是成功的当且仅当 B_{qrm} \subseteq B_{vot}

Paxos 的数学家在一个由多轮表决构成的集合上定义了3个条件,然后展示了如果已经进行过的表决满足这些条件,那么一致性会得到保证,进行性也是可能的。头两个条件比较简单,可以被非正式的描述如下:

B1(\beta)\beta 中的每一轮表决,都有一个唯一的编号
B2(\beta)\beta 中任意两轮表决的法定人数集中,至少有一个公共的牧师成员
B3(\beta) :对于 \beta 中每一轮表决 B ,如果 B 的法定人数集中的任何一个牧师在 B 中一个更小轮的表决中投过(赞成)票,那么 B 的法令与所有这些更小轮表决中最大那次表决法令相同。

下图帮助诠释了这一段隐晦的文本。手稿画出了5个牧师

  • 编号为2的表决是最小(早)的表决,从而这轮投票的条件也都满足
  • 编号为5的表决中没有牧师在更小号的投票中投过票,这轮的条件也满足
  • 这轮表决的定额集合中,唯一一个在更小编号的表决中投过票的牧师是 \Delta,他在编号为2的表决中投了票,所以要求这轮的法令和2的法令必须相等,成立
  • 这是一次成功的表决。27轮表决的定额集合是 A、\Gamma、\Delta 。牧师 A 没有在更小号的表决中投过票。投过票的小编号表决只有2和5,这两个更小编号表决中最大的是5,所以条件要求本轮的法令和5的法令相同,成立
  • 本轮的定额集合是 B、\Gamma、\DeltaB 投过票的小编号表决只有14, \Gamma 在表军5和27中投过票,\Delta 在表决2和27中投过票,其中表决最大的一个是27,所以条件要求29的法令和27的法令相同。

正式的表述 B1(\beta)-B3(\beta) 需要更多的符号标记。我们用符号 v 表示一个投票,那么 v 包含 3个组成部分:投票的牧师 v_{pst} ,本轮表决的编号 v_{bal} 和所表决的法令 v_{dec} 。Paxos人同时定义了 v_{bal} = -\infty , v_{dec} = BLANK 的投票 vnull 投票。对于 -\infty < b < \infty 的所有编号为 b 的表决,不会以 BLANK 作为法令。对于任意牧师 p ,他们定义了 null_{p} 作为唯一的 null 投票 v , v_{pst} = p

Paxos数学家为所有投票定义了一个全局顺序,但是手稿里包含了这个定义那一部分遗失了,剩下的片段显示,对于任意 vv' ,如果 v_{bal} < v'_{bal} ,那么 v < v'

对于任意的表决组成的集合 \beta ,定义集合 Votes(\beta) 为包含所有满足如下条件的投票 v

\exists B \in \beta ,使得 v_{pst} \in B_{vot},v_{bal} = B_{bal}, v_{dec} = B_{dec} ,(即用 Votes(\beta) 表示所有在 \beta 中的投票)。

如果 p 是一个议员, b 是一个表决的编号或 \pm\infty ,则 MaxVote(b, p,\beta) 定义为\beta 中由 p 投出的表决编号小于 b 的最大的投票 v 或者 null_p 。 因为 null_p 比所有 p 实际投出的票都要小,这就意味着 MaxVote(b, p, \beta) 是下面集合中的最大投票:

{v \in Votes(\beta): (v_{pst} = p) \land (v_{bal} < b)} \cup {null_p}

对于任意非空的牧师集合 QMaxVote(b,Q, \beta) 定义为对于所有 p \in Q ,所有 MaxVote(b,p,\beta) 中的最大值。

于是,条件 B1(\beta)-B3(\beta) 正式表述如下:

  • B1(\beta) \triangleq \forall B, B' \in \beta: (B \not =B') \Rightarrow (B_{bal} \not = B'_{bal})
  • B2(\beta) \triangleq \forall B, B' \in \beta: B_{qrm} \cap B'_{qrm} \not = \phi
  • B3(\beta) \triangleq \forall B \in \beta: MaxVote(B_{bal}, B_{qrm}, \beta)_{bal} \not = - \infty

\Rightarrow (B_{dec} = MaxVote(B_{bal}, B_{qrm}, \beta)_{dec})