Paxos 算法详解

发布于 2023-09-15 08:33:59 字数 10284 浏览 39 评论 0

作为分布式一致性代名词的 Paxos 算法号称是最难理解的算法。本文试图用通俗易懂的语言讲述 Paxos 算法。

一、Paxos 算法背景

Paxos 算法是 Lamport 宗师提出的一种基于消息传递的分布式一致性算法,使其获得 2013 年图灵奖。

Paxos 由 Lamport 于 1998 年在 The Part-Time Parliament 论文中首次公开,最初的描述使用希腊的一个小岛 Paxos 作为比喻,描述了 Paxos 小岛中通过决议的流程,并以此命名这个算法,但是这个描述理解起来比较有挑战性。后来在 2001 年,Lamport 觉得同行不能理解他的幽默感,于是重新发表了朴实的算法描述版本 Paxos Made Simple。

自 Paxos 问世以来就持续垄断了分布式一致性算法,Paxos 这个名词几乎等同于分布式一致性。Google 的很多大型分布式系统都采用了 Paxos 算法来解决分布式一致性问题,如 Chubby、Megastore 以及 Spanner 等。开源的 ZooKeeper,以及 MySQL 5.7 推出的用来取代传统的主从复制的 MySQL Group Replication 等纷纷采用 Paxos 算法解决分布式一致性问题。

然而,Paxos 的最大特点就是难,不仅难以理解,更难以实现。

二、Paxos 算法流程

Paxos 算法解决的问题正是分布式一致性问题,即一个分布式系统中的各个进程如何就某个值(决议)达成一致。

Paxos 算法运行在允许宕机故障的异步系统中,不要求可靠的消息传递,可容忍消息丢失、延迟、乱序以及重复。它利用大多数 (Majority) 机制保证了 2F+1 的容错能力,即 2F+1 个节点的系统最多允许 F 个节点同时出现故障。

一个或多个提议进程 (Proposer) 可以发起提案 (Proposal),Paxos 算法使所有提案中的某一个提案,在所有进程中达成一致。系统中的多数派同时认可该提案,即达成了一致。最多只针对一个确定的提案达成一致。

Paxos 将系统中的角色分为提议者 (Proposer),决策者 (Acceptor),和最终决策学习者 (Learner):

  • Proposer: 提出提案 (Proposal)。Proposal 信息包括提案编号 (Proposal ID) 和提议的值 (Value)。
  • Acceptor:参与决策,回应 Proposers 的提案。收到 Proposal 后可以接受提案,若 Proposal 获得多数 Acceptors 的接受,则称该 Proposal 被批准。
  • Learner:不参与决策,从 Proposers/Acceptors 学习最新达成一致的提案(Value)。

在多副本状态机中,每个副本同时具有 Proposer、Acceptor、Learner 三种角色。

Paxos 算法中的角色

Paxos 算法通过一个决议分为两个阶段(Learn 阶段之前决议已经形成):

  1. 第一阶段:Prepare 阶段。Proposer 向 Acceptors 发出 Prepare 请求,Acceptors 针对收到的 Prepare 请求进行 Promise 承诺。
  2. 第二阶段:Accept 阶段。Proposer 收到多数 Acceptors 承诺的 Promise 后,向 Acceptors 发出 Propose 请求,Acceptors 针对收到的 Propose 请求进行 Accept 处理。
  3. 第三阶段:Learn 阶段。Proposer 在收到多数 Acceptors 的 Accept 之后,标志着本次 Accept 成功,决议形成,将形成的决议发送给所有 Learners。

Paxos 算法流程

Paxos 算法流程中的每条消息描述如下:

  • Prepare: Proposer 生成全局唯一且递增的 Proposal ID (可使用时间戳加 Server ID),向所有 Acceptors 发送 Prepare 请求,这里无需携带提案内容,只携带 Proposal ID 即可。
  • Promise: Acceptors 收到 Prepare 请求后,做出“两个承诺,一个应答”。

两个承诺:

  1. 不再接受 Proposal ID 小于等于(注意:这里是<= )当前请求的 Prepare 请求。
  2. 不再接受 Proposal ID 小于(注意:这里是< )当前请求的 Propose 请求。

一个应答:

不违背以前作出的承诺下,回复已经 Accept 过的提案中 Proposal ID 最大的那个提案的 Value 和 Proposal ID,没有则返回空值。

  • Propose: Proposer 收到多数 Acceptors 的 Promise 应答后,从应答中选择 Proposal ID 最大的提案的 Value,作为本次要发起的提案。如果所有应答的提案 Value 均为空值,则可以自己随意决定提案 Value。然后携带当前 Proposal ID,向所有 Acceptors 发送 Propose 请求。
  • Accept: Acceptor 收到 Propose 请求后,在不违背自己之前作出的承诺下,接受并持久化当前 Proposal ID 和提案 Value。
  • Learn: Proposer 收到多数 Acceptors 的 Accept 后,决议形成,将形成的决议发送给所有 Learners。

Paxos 算法伪代码描述如下:

Paxos 算法伪代码

  1. 获取一个 Proposal ID n,为了保证 Proposal ID 唯一,可采用时间戳+Server ID 生成;
  2. Proposer 向所有 Acceptors 广播 Prepare(n) 请求;
  3. Acceptor 比较 n 和 minProposal,如果 n>minProposal,minProposal=n,并且将 acceptedProposal 和 acceptedValue 返回;
  4. Proposer 接收到过半数回复后,如果发现有 acceptedValue 返回,将所有回复中 acceptedProposal 最大的 acceptedValue 作为本次提案的 value,否则可以任意决定本次提案的 value;
  5. 到这里可以进入第二阶段,广播 Accept (n,value) 到所有节点;
  6. Acceptor 比较 n 和 minProposal,如果 n>=minProposal,则 acceptedProposal=minProposal=n,acceptedValue=value,本地持久化后,返回;否则,返回 minProposal。
  7. 提议者接收到过半数请求后,如果发现有返回值 result >n,表示有更新的提议,跳转到 1;否则 value 达成一致。

下面举几个例子,实例 1 如下图:

Paxos 算法实例 1

图中 P 代表 Prepare 阶段,A 代表 Accept 阶段。3.1 代表 Proposal ID 为 3.1,其中 3 为时间戳,1 为 Server ID。X 和 Y 代表提议 Value。

实例 1 中 P 3.1 达成多数派,其 Value(X) 被 Accept,然后 P 4.5 学习到 Value(X),并 Accept。

Paxos 算法实例 2

实例 2 中 P 3.1 没有被多数派 Accept(只有 S3 Accept),但是被 P 4.5 学习到,P 4.5 将自己的 Value 由 Y 替换为 X,Accept(X)。

Paxos 算法实例 3

实例 3 中 P 3.1 没有被多数派 Accept(只有 S1 Accept),同时也没有被 P 4.5 学习到。由于 P 4.5 Propose 的所有应答,均未返回 Value,则 P 4.5 可以 Accept 自己的 Value (Y)。后续 P 3.1 的 Accept (X) 会失败,已经 Accept 的 S1,会被覆盖。

Paxos 算法可能形成活锁而永远不会结束,如下图实例所示:

Paxos 算法形成活锁

回顾两个承诺之一,Acceptor 不再应答 Proposal ID 小于等于当前请求的 Prepare 请求。意味着需要应答 Proposal ID 大于当前请求的 Prepare 请求。

两个 Proposers 交替 Prepare 成功,而 Accept 失败,形成活锁(Livelock)。

三、Multi-Paxos 算法

原始的 Paxos 算法(Basic Paxos)只能对一个值形成决议,决议的形成至少需要两次网络来回,在高并发情况下可能需要更多的网络来回,极端情况下甚至可能形成活锁。如果想连续确定多个值,Basic Paxos 搞不定了。因此 Basic Paxos 几乎只是用来做理论研究,并不直接应用在实际工程中。

实际应用中几乎都需要连续确定多个值,而且希望能有更高的效率。Multi-Paxos 正是为解决此问题而提出。Multi-Paxos 基于 Basic Paxos 做了两点改进:

  1. 针对每一个要确定的值,运行一次 Paxos 算法实例(Instance),形成决议。每一个 Paxos 实例使用唯一的 Instance ID 标识。
  2. 在所有 Proposers 中选举一个 Leader,由 Leader 唯一地提交 Proposal 给 Acceptors 进行表决。这样没有 Proposer 竞争,解决了活锁问题。在系统中仅有一个 Leader 进行 Value 提交的情况下,Prepare 阶段就可以跳过,从而将两阶段变为一阶段,提高效率。

Multi-Paxos 流程

Multi-Paxos 首先需要选举 Leader,Leader 的确定也是一次决议的形成,所以可执行一次 Basic Paxos 实例来选举出一个 Leader。选出 Leader 之后只能由 Leader 提交 Proposal,在 Leader 宕机之后服务临时不可用,需要重新选举 Leader 继续服务。在系统中仅有一个 Leader 进行 Proposal 提交的情况下,Prepare 阶段可以跳过。

Multi-Paxos 通过改变 Prepare 阶段的作用范围至后面 Leader 提交的所有实例,从而使得 Leader 的连续提交只需要执行一次 Prepare 阶段,后续只需要执行 Accept 阶段,将两阶段变为一阶段,提高了效率。为了区分连续提交的多个实例,每个实例使用一个 Instance ID 标识,Instance ID 由 Leader 本地递增生成即可。

Multi-Paxos 允许有多个自认为是 Leader 的节点并发提交 Proposal 而不影响其安全性,这样的场景即退化为 Basic Paxos。

Chubby 和 Boxwood 均使用 Multi-Paxos。ZooKeeper 使用的 Zab 也是 Multi-Paxos 的变形。

附 Paxos 算法推导过程

Paxos 算法的设计过程就是从正确性开始的,对于分布式一致性问题,很多进程提出(Propose)不同的值,共识算法保证最终只有其中一个值被选定,Safety 表述如下:

  • 只有被提出(Propose)的值才可能被最终选定(Chosen)。
  • 只有个值会被选定(Chosen)。
  • 进程只会获知到已经确认被选定(Chosen)的值。

Paxos 以这几条约束作为出发点进行设计,只要算法最终满足这几点,正确性就不需要证明了。Paxos 算法中共分为三种参与者:Proposer、Acceptor 以及 Learner,通常实现中每个进程都同时扮演这三个角色。

Proposers 向 Acceptors 提出 Proposal,为了保证最多只有个值被选定(Chosen),Proposal 必须被超过一半的 Acceptors 所接受(Accept),且每个 Acceptor 只能接受一个值。

为了保证正常运行(必须有值被接受),所以 Paxos 算法中:

P1:Acceptor 必须接受(Accept)它所收到的第一个 Proposal。

先来先服务,合情合理。但这样产生一个问题,如果多个 Proposers 同时提出 Proposal,很可能会导致无法达成一致,因为没有 Propopal 被超过一半 Acceptors 的接受,因此,Acceptor 必须能够接受多个 Proposal,不同的 Proposal 由不同的编号进行区分,当某个 Proposal 被超过一半的 Acceptors 接受后,这个 Proposal 就被选定了。

既然允许 Acceptors 接受多个 Proposal 就有可能出现多个不同值都被最终选定的情况,这违背了 Safety 要求,为了保证 Safety 要求,Paxos 进一步提出:

P2:如果值为 v 的 Proposal 被选定(Chosen),则任何被选定(Chosen)的具有更高编号的 Proposal 值也一定为 v。

只要算法同时满足P1P2,就保证了 Safety。P2是一个比较宽泛的约定,完全没有算法细节,我们对其进一步延伸:

P2a:如果值为 v 的 Proposal 被选定(Chosen),则对所有的 Acceptors,它们接受(Accept)的任何具有更高编号的 Proposal 值也一定为 v。

如果满足P2a则一定满足P2,显然,因为只有首先被接受才有可能被最终选定。但是P2a依然难以实现,因为 acceptor 很有可能并不知道之前被选定的 Proposal(恰好不在接受它的多数派中),因此进一步延伸:

P2b:如果值为 v 的 Proposal 被选定(Chosen),则对所有的 Proposer,它们提出的的任何具有更高编号的 Proposal 值也一定为 v。

更进一步的:

P2c:为了提出值为 v 且编号为 n 的 Proposal,必须存在一个包含超过一半 Acceptors 的集合 S,满足(1) 没有任何 S 中的 Acceptors 曾经接受(Accept)过编号比 n 小的 Proposal,或者(2) v 和 S 中的 Acceptors 所接受过(Accept) 的编号最大且小于 n 的 Proposal 值一致。

满足P2c即满足P2b即满足P2a即满足P2。至此 Paxos 提出了 Proposer 的执行流程,以满足P2c

  1. Proposer 选择一个新的编号 n,向超过一半的 Acceptors 发送请求消息,Acceptor 回复: (a) 承诺不会接受编号比 n 小的 proposal,以及(b) 它所接受过的编号比 n 小的最大 Proposal(如果有)。该请求称为 Prepare 请求。
  2. 如果 Proposer 收到超过一半 Acceptors 的回复,它就可以提出 Proposal,Proposal 的值为收到回复中编号最大的 Proposal 的值,如果没有这样的值,则可以自由提出任何值。
  3. 向收到回复的 Acceptors 发送 Accept 请求,请求对方接受提出的 Proposal。

仔细品味 Proposer 的执行流程,其完全吻合P2c中的要求,但你可能也发现了,当多个 Proposer 同时运行时,有可能出现没有任何 Proposal 可以成功被接受的情况(编号递增的交替完成第一步),这就是 Paxos 算法的 Liveness 问题,或者叫“活锁”,论文中建议通过对 Proposers 引入选主算法选出 Distinguished Proposer 来全权负责提出 Proposal 来解决这个问题,但是即使在出现多个 Proposers 同时提出 Proposal 的情况时,Paxos 算法也可以保证 Safety。

接下来看看 Acceptors 的执行过程,和我们对P2做的事情一样,我们对P1进行延伸:

P1a:Acceptor 可以接受(Accept)编号为 n 的 Proposal 当且仅当它没有回复过一个具有更大编号的 Prepare 消息。

易见,P1a包含了P1,对于 Acceptors:

  1. 当收到 Prepare 请求时,如果其编号 n 大于之前所收到的 Prepare 消息,则回复。
  2. 当收到 Accept 请求时,仅当它没有回复过一个具有更大编号的 Prepare 消息,接受该 Proposal 并回复。

以上涵盖了满足P1aP2b的一套完整一致性算法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

陌若浮生

暂无简介

0 文章
0 评论
638 人气
更多

推荐作者

内心激荡

文章 0 评论 0

JSmiles

文章 0 评论 0

左秋

文章 0 评论 0

迪街小绵羊

文章 0 评论 0

瞳孔里扚悲伤

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文