经典Paxos算法笔记

介绍

Paxos算法是一个高容错性的分布式一致性算法。去年学习过Paxos算法,一直没将整理到博客。现在将经典Paxos算法相关内容整理到博客上。
经典Paxos算法本身也并不是太难理解,Lamport从期望的结果出发,通过增强条件一步步反推,最终发掘出可以保证了系统一致性的Paxos算法。如果能仔细体会到这其中一步步的反推,就会觉得Paxos是个很自然的东西了。

首先了解一下Paxos算法的作者Leslie Lamport (个人官网),由于对分布式领域的贡献获得2013年度图灵奖。
这里有一段Lamport获得图灵奖的访谈。他也在TeX基础上发明了LaTeX排版系统

Lamport在1982年与Robert Shostak和Marshall Pease一起发表了The Byzantine Generals Problem,也即著名的拜占庭将军问题。拜占庭将军问题揭示了在异步系统和不可靠通道上达到一致性是不可能的。

在1990年Lamport发表了论文The Part-Time Parliament,这就是Paxos的原始论文,当时没什么人能够懂这篇论文因为其用了讲故事的形式进行算法描述。Lamport拒绝主编要求修改论文的提案,因此论文最终被撤销。
在1997年MIT的Nancy Lynch发布了Paxos原文的修改版Revisiting the Paxos algorithm
这里补充一下,Nancy Lynch曾发表过论文Impossibility of Distributed Consensus with One Faulty Process引出FLP impossibility。
在1998年原论文最终被ACM Transactions on Computer Systems接受。
在2001年Lamport对原文重述,发表了Paxos Made Simple。此版论文简洁易懂,没有复杂数学公式,也成了大多数人学习Paxos的入口。

内容

由于Paxos Made Simple文章本身语言很简洁,本文也尽可能用简洁的表述来呈现Paxos算法。
可以说Paxos算法本身是基于期望的目标/结果的一步步反推并强化条件而得出的。

背景

有一些进程可以发起提案,一致性算法要做到以下几点:

  1. 所有提出的提案中最终只有一个会被选定。
  2. 如果提出任何提案,则就没有被选定的提案。
  3. 如果一个提案被选定,进程应该要能获取到被选定的提案。

对于一致性算法的安全性,需要满足以下3点:

  1. 只有被提出的提案才能被选定。
  2. 只有一个提案能够被选定。
  3. 任何进程认为选定的提案必须是真正被选定的提案。

Pxos算法的目标就是要能够做到以上3点。

这里需要说明什么叫选定:一种很好的判断是否选定的方式是:对提案表决的进程中大多数批准提案。所谓大多数,可以简单理解为超过一半,任意两个大多数的集合一定是有交集的。

角色

定义三种参与者:
Proposer 可以发起提案
Acceptor 可以对Proposer的提案批准/拒绝
Learner 获取最终被选定的提案

这三种角色只是逻辑上的一种区分,在具体实现完全可以一个进程充当多种角色。

推导

假设没有失败/消息丢失,只提出一个提案,也可以选出最终提案,这意味着:

P1: 一个Acceptor必须批准它收到的第一个提案。

满足P1是不够的,因为很可能最终没有提案被大多数Acceptor批准。(这不难想象)

所以P1加上一个提案需要被多数派批准才能选定意味着一个Acceptor必须可以批准不止一个提案,否则很可能产生不出一个被多数派批准的提案。

我们这里可以给每个提案分配一个唯一的自然数作为编号,将提案表示为<id, value>的形式(其中id为编号,value为提案本身),这样可以用来区分每个Acceptor批准的不同提案。于是我们可以说,如果值为某个value的提案被多数派批准后,则该提案被选定。

上面已经将提案的概念演化为由编号与提案表示的一个组合了,这样的话可以有多个<id, value>表示的提案被选定。但必须要保证,所有被选定的提案的value值必须是同一个,否则就违背了一致性算法的初衷了。这里引出:

P2: 如果一个值为v的提案被选定了,则所有被选定的具有更高编号的提案值也必须是v。

P2保证了上文提及的一致性算法的安全性的第2点,即只能有一个提案被选定。因为一个提案要被至少一个Acceptor批准才能能够选定,所以可以通过满足下面引出的P2a来满足P2:

P2a: 如果一个值为v的提案被选定了,则被任一Acceptor批准的具有更高编号的提案值也必须是v。

可以看出,P2a是P2根据一个提案被选定肯定是需要先经过被Acceptor批准出发而得出的强化条件,满足了P2a显然能够满足P2。

这里产生了一个问题,由于通信是异步的,某个Acceptor还没有收到任何提案时,可能系统已经产生了某个被选定的提案,此时这个Acceptor接收到了另一个Proposer发出的具有更高编号的提案,根据P1需要批准,这样的话就与P2a矛盾了。也就是说P2a与P1不兼容

P2a根据提案的被选定的上一步是需要被Acceptor批准出发,强化P2得出P2a。那么这里可以用类似思路,根据一个提案得先被Proposer提出才能被Acceptor批准,进一步强化p2a得出:

P2b: 如果一个值为v的提案被选定了,则之后任一Proposer产生的编号更高的填,value值也必须是v。

P2b可以看作是对Proposer提出提案作出要求,但P2b仍然是很难实现的。下面引出P2c:

P2c: 如果一个提案<n, v>被提出,则必须要存在一个多数Acceptor集S满足如下条件之一:
a) S中任一Acceptor都没有批准过编号小于n的提案。
b) S中Acceptor批准过的编号最大的小于n的提案的值为v。

这其中a)可以看作是初始的边界条件。

只要维持P2c的不变性就可以满足P2b。接下来就通过第二数学归纳法简单证明一下,假定一个提案<n, v>被选定,通过维持P2c可以满足P2b:
假定此时一个Proposer要提出<m, v'>的提案的话:

  1. 当m=n+1时,根据P2c的b),满足存在一个多数Acceptor集S'满足批准过编号最大的小于n的提案值为v'。那么设批准<n, v>的多数Acceptor集为S,则S与S'必然存在交集。对于这些交集Acceptor而言,“批准过的编号最大的小于n的提案”便是<n, v>,则推得v'=v成立。所以在m=n+1时,提出的提案<m, v'> = <n+1, v'> = <n+1, v>。

  2. 假设在编号在[n+1, m-1]的提出的提案的值都为v,需要证明P2b也就是此时v'=v:根据P2c,v'应该等于某个多数Acceptor集S'中Acceptor批准的编号最大的提案的值。
    1). 假设S'中Acceptor批准过编号最大的提案的编号在[n+1, m-1]范围,则根据归纳假设,提案值显然为v。
    2). 假设S'中Acceptor批准过编号最大的提案的编号为n,则此时S'必然与批准<n, v>的那个多数Acceptor集S有交集,而S的所有Acceptor都批准了<n, v>,由此可得提案值为v。

由此得证在满足P2c的条件下,P2b也成立。

算法过程

生成提案

上面P2c中我们可以看出,对于一个要提出提案的Proposer而言,最重要的就是要知道Acceptor已经批准过编号最大的提案是什么。
如果某个Proposer想要提出编号为n的协议,对于Acceptor而言,告诉Proposer它们已经批准过的编号小于n中编号最大的提案是很容易的。但是编号小于n中其余剩下还未被批准的提案(可能是还Acceptor还未收到或者还未响应)后续可能会对结果造成难以预期的影响。Paxos算法中为了避免这种不确定因素,需要Acceptor保证不会再批准任何编号小于n的提案。

这里引出提案生成法:

  1. Proposer选择一个自己的提案编号n,向Acceptor集合(或者一个多数集可)发送Prepare请求,要求Acceptor回应:

    • 承诺保证不再批准任何编号小于n的提案。
    • 如果已经批准了任何提案,告诉Proposer已经批准的最大的小于n的提案值。
  2. 如果Proposer收到了多数派Acceptor响应,则可以根据P2c产生提案。即如果这些Acceptor都没有批准过任何提案,则可以随意选定一个提案值v,否则选择这些响应Acceptor中具有最大编号的提案值。

此时Proposer可以向Acceptor集合(或者一个多数集也可)发送Accept请求请求其批准提案。

批准提案

Acceptor可以在任何时候都可以响应Prepare请求。在不违背已经响应过的承诺的前提下,可以响应Accept请求批准提案。

下面引出对于Acceptor如何对提案的处理逻辑:

P1a: 一个Acceptor批准某个提案当且仅当它没有响应过具有更高编号的Prepare请求。

P1a是对P1的一种强化。

优化

对于Prepare请求,有一个优化点:如果一个Acceptor已经响应了编号为n的Prepare请求,则可以忽略后续任何编号小于n的Prepare请求。因为根据对编号为n的Prepare请求的承诺,Acceptor是不会批准编号小于n的提案,自然没必要响应其Prepare请求了。所以其实对Acceptor而言,只需要维护其响应过的Prepare请求中的最大编号以及批准过的提案中的最大编号即可。一个Proposer只要保证不会产生重复编号的提案,可以随意丢弃提出的提案。

完整的算法描述

综合上述的提交提案与批准提案,下面给出完整的算法描述。
阶段1:

  1. Proposer选择一个提案编号n,向Acceptor的一个多数派集合发送Prepare请求。
  2. Acceptor如果收到编号为n的Prepare请求,如果已经响应过编号大于n的Prepare请求,则可以选择忽略该Prepare请求。否则承诺不会批准编号小于n的提案,并且如果已经有批准过编号小于n的提案的话需要回复给Proposer。

阶段2:

  1. 如果Proposer收到了超过半数的Prepare请求响应,则会根据响应确定提案值,向Acceptor集合(要足以形成多数派,可以不是原先那些)发送Accept请求。
  2. 收到Accept请求的Acceptor只要没有响应过编号更大的Prepare请求,就可以通过提案。

参考资料

The Part-Time Parliament
Paxos Made Simple
《从Paxos到ZooKeeper》
http://www.cs.yale.edu/homes/aspnes/pinewiki/Paxos.html

posted @ 2017-09-06 00:06  活在夢裡  阅读(832)  评论(0编辑  收藏  举报