Paxos 算法详解

欢迎关注一下我的 知乎账号,以后主要在知乎分享内容。感谢~
https://www.zhihu.com/people/ypjiang96/posts

很久之前看过 Paxos Made Simple 这篇论文,当时很多地方没有弄清楚就放下了。最近重新看 Paxos 算法的时候,发现网上的很多资料都云里雾里,于是决定还是来看第一手资料,理解又加深了不少,这里记录一下。以下内容主要参考了 Paxos Made Simple 这篇论文和 Raft 作者写的 Paxos Summary 文档。即使你已经很熟悉 Paxos 了,后文关于歧义的分析也可能值得一看。

前言

首先需要明确的一个点是,Paxos 和 Multi-Paxos 区别是什么?
我们知道 Paxos 是一个共识算法,共识算法的定义就是多个节点对一个值达成一致 (one-shot consensus)。而实际应用中,我们更希望能对一组值达成一致(也就是复制状态机,SMR),每个值需要一次 Paxos,连续起来就是 Multi-Paxos。这里其实 Multi-Paxos 可以理解为 Paxos 的简单叠加,不过我们可以进行优化,当然这也导致了额外的复杂度。
这篇文章我们先来学习 Paxos 算法。

Paxos

共识算法是为了使得 N 个节点对某个 transaction 达成一致,这里一个节点可以理解为一个进程,我们称这 N 个节点组成了一个分布式系统。对于 N 个节点,可能有多个不同的 transaction 输入,但是最终所有节点都同意一个相同的输出。

当 N = 1 时,直接输出第一个输入的 transaction 就行了。但是如果这一个节点宕机,那么在节点重启恢复正常之前整个系统是处于不可用的状态,为了保证高可用,我们需要多个节点,即 N > 1,即使部分节点宕机,我们也能从其它节点获取系统最终的输出 transaction。

有证明指出,在 CFT 模型下,如果系统需要容忍 f 个节点宕机,那么至少需要 2f +1 个节点 。

每个节点可能承担三种角色,分别是 proposer、acceptor、learner。proposer 提供输入,acceptor 决定是否接受该输入,learner 决定最终输出,一个节点可以同时是三种角色。一般而言系统所有节点都是 acceptor。如果一个 acceptor 最多只能接受一个 transaction,那么当某个 transaction 被 f+1 个 acceptor 接受时,我们称该 transaction 被 chosen(注意此时只是处于上帝视角的我们知道该 transaction 被 chosen了,learner 则还需要一个或者两个网络延时才能直到该 transaction 被 chosen)。

下面我们开始推导 Paxos 算法。

P1

首先考虑最简单的情况,没有节点宕机,网络没有丢包。(P1)那么当一个 proposer 提议一个 transaction 时,所有 acceptor 必须接受收到的第一个 transaction。

这是因为一共就提议了一个 transaction,如果 acceptor 不接受该 transaction,那么就不可能有 transaction 被 chosen,系统就失去了 liveness。

但是 P1 导致了一个问题:当多个 proposer 几乎同时提议多个不同的 transaction 时,可能导致不存在 f+1 个节点接受同一个 transaction。
为了解决该问题,我们需要允许 acceptor 可以接受不止一个 transaction。

Paxos 算法被分为连续递增的 view,可以用自然数标记每个 view。在每个 view 中,acceptor 最多只能接受一个 transaction。(这里的 view 可以对应到论文里的 proposal id)。

由于每个 acceptor 可以接受多个 transaction,那么根据 chosen 的定义,就可能存在多个 transaction 被 chosen,系统就失去了 safety。

P2

而共识算法要求只能有一个 transaction 被 chosen,那么我们需要要求这些被 chosen 的 transaction 都相同,即 (P2) 如果某个 transaction=tx1 在 view=m 被 chosen 了,那么在 view=n > m 被 chosen 的 transaction 和 tx1 相同。

如果我们能保证:(P2a) 如果某个 transaction=tx1 在 view=m 被 chosen 了,那么在 view=n > m 被 acceptor 接受的 transaction 和 tx1 相同,我们就满足了 P2.

这是因为,一个 transaction 要想被 chosen,那么至少先被一个 acceptor 接受。如果在大于 m 的 view 中接受的都是 tx1,那么就不可能有其它 transaction 被 chosen。

基于目前的分析:为了 liveness,我们需要满足 P1,为了 safety,我们需要满足 P2a.

注意,由于网络是异步的,可能存在一个 节点c 从未收到任何 transaction,但是系统已经 chosen 了某个 transaction。假如此时有一个 proposer 提议了另一个 transaction,而节点 c 刚好收到该 transaction,那么根据 P1,节点c 必须接受该 transaction,而这却违反了 P2a.

我们又陷入了困境。

不过我们发现,P2a 这个条件太弱了。

我们可以加强一下 P2a,记为 (P2b) 如果某个 transaction=tx1 在 view=m 被 chosen 了,那么在 view=n >m 被 proposer 提议的 transaction 都和 tx1 相同。

这是因为,acceptor 只会接受 proposer 提议的值。如果在大于 m 的 view 中提议的都是 tx1,那么就不可能有其它 transaction 被接受。
也就是说,P2b 蕴含 P2a,P2a蕴含 P2。

下面我们考虑如何才能满足 P2b.

如果 tx1 在 view=m 被 chosen,那么说明至少 f+1 个 acceptor 在 view=m 接受了 tx1,要想保证当 view>m 时被 propose 的都是 tx1,proposer 提议之前就需要询问这 f+1 个节点中的至少一个节点,然后直接 propose 从该节点收到的 tx1。

因为这 f+1 个节点对于其它 proposer 而言可能是未知的,为了保证 proposer 能访问到其中至少一个节点,需要让 proposer 去询问至少 f+1 个节点,这两组 f+1 节点的交集至少会有一个共同节点。

于是,只需要满足 (P2c) 对于被提议的 view=v 和 transaction=tx,一定存在 f+1 个 acceptor,要么这 f+1 个 acceptor 在 view<v 时从未接受任何 transaction(此时 proposer 可以任意选择 transaction);要么 tx 是这 f+1 个 acceptor 中任意一个 acceptor 曾接受过的 view 最高(但是小于 v)的 transaction。

也就是 proposer 在 view=v 期间提议之前需要先去 learn 任意 f+1 个 acceptor 在 view<v 期间接受的 transaction。如果不存在被接受的 transaction,该 proposer 可以任意提议一个 transaction;如果存在,就必须提议该 transaction,同时要求这 f+1 个 acceptor 不能再接受任何 view<n 的 transaction (这句话对应论文里预测未来的部分:由于无法预测未来可能接受的 transaction,直接禁止接受任何 view<n 的 transaction)。

于是我们得到了 proposer 提议 transaction 的算法:

  1. proposer 选择一个 viewNumber,假设是 n,向 acceptor 发送请求 (论文中称为 prepare request),获取如下回复:
    1. acceptor 承诺不再接受任何viewNumber<n 的 transation
    2. acceptor 在 viewNumber<n 时收到的最新的 transaction
  2. 当 proposer 收到 f+1 个回复,就可以提议 viewNumber=n 的 transaction 了。如果收到的 f+1 个回复中不含任何 transaction,那么 proposer 就可以任意选择 transaction 来提议;否则选择 f+1 个回复中 viewNumber 最高的 transaction 进行提议。 (论文中称为 accept request)

对于 acceptor,可能收到 prepare request 和 accept request。 acceptor 可以直接拒绝任何 request,这不影响系统的 safety。所以我们只需要限制 acceptor 何时可以回复 request:

  1. 收到 prepare request 时,总是可以回复
  2. 收到 accept request 时,如果没有保证过不接受该 view 的 transaction,就可以接受该 transaction 并回复

两个优化:

  1. 当 acceptor 收到 prepare request 时,如果该 acceptor 已经回复过更高 view 的 prepare request,就可以忽略这个 prepare request,同时可以回复已知的最大 viewNumber,避免该 proposer 做徒劳的尝试。
  2. 同样,如果 acceptor 收到一个 accept request,但是该 acceptor 已经回复过更高 view 的 prepare request,就忽略这个 accept request,同时可以回复已知的最大 viewNumber,避免该 proposer 做徒劳的尝试。

持久化

acceptor 在回复消息前需要先做持久化的变量:

  1. viewNumber:prepare request 中携带的 viewNumber
  2. transaction:最新接受的 transaction

proposer 一定不能在一个 view 中提议两个不同的 transaction,会影响 safety。
因此 proposer 需要持久化使用过的最大的 viewNumber,宕机重启后,可以递增 viewNumber 然后开始提议其它 transaction。

如何 learn 被 chosen 的 transaction

方案一:每个 acceptor 接受一个 transaction 时都广播一个消息通知所有的 learner,当 learnder 收到 f+1 个通知时,说明该 transaction 被 chosen 了。但是该方案会导致 O(N^2) 的消息复杂度。

方案二:由于假设的是非拜占庭环境,可以通过一个 leading learner 来通知 chosen transaction。也就是所有的 acceptor 只发给 leading learner 通知,其它 learner 可以从 leading learner 学习 chosen 的结果。从而降低了消息复杂度。

活锁

上述算法存在活锁问题。

我们很容易构造一个活锁的场景:proposer p1 使用 viewNumber=v1 完成了 prepare 阶段后,在开始 accept 阶段前,此时 proposer p2 使用 viewNumber=v2 > v1 完成了 prepare 阶段(此时 acceptor 保证不再接受任何小于 v2 的 transaction),那么 proposer p1 的 accept 阶段就会被拒绝,于是 proposer p1 使用 viewNumber=v3 发起 prepare 阶段,又导致 proposer p2 的 accept 阶段被拒绝,如此循环下去,导致没有任何 transaction 可以被 chosen…

为了保证 liveness,我们需要使用 leader,只有 leader 拥有提议权。那么,当系统只存在一个 leader 时,一定可以保证 liveness;如果存在多个 leader,则可能会持续上述的活锁过程直到只有一个 leader(其它 leader 都过期退位)。

这里的 leader 和 Raft 中的不同。Paxos 中的 leader 是弱 leader,可能同时存在多个,而 Raft 是强 leader,最多只能同时存在一个。

歧义

Paxos Made Simple 原始论文里有一句话存在歧义,Lamport 在自己的主页也说了 “Do not try to implement the algorithm from this paper.”。

论文第八页上方的括号里写了这么一句话:“This need not be the same set of acceptors that responded to the initial requests.”,是说 proposer 在 accept 阶段选择的 acceptor 没有必要与 prepare 阶段选择的 acceptor 完全一致。

这句话的本意是:我给你们个提示,可以通过优化算法,使得这两个阶段不需要选择完全一致的 acceptor,但是我现在不想在这里写了。其实也就是 The Part-Time Parliament 中描述的算法。

但是,很容易误解为在 Paxos Made Simple 描述的算法中,两个阶段选择的 acceptor 可以不必完全一致。具体可以参考 stack overflow 上的这个问题。

其实优化的点也很简单:acceptor 在同意接受 accept request 后,需要更新 viewNumber 为 accept request 中携带的 viewNumber。

总结&思考

很久之前看 Paxos Made Simple 这篇论文的时候,感觉非常难以理解,尤其是 P2c 那部分看得云里雾里。后来去看了很多其它的共识方面的论文,再回过头来看 Paxos 时,才理解题目中的 Simple 的意义。

问题分困难 (Hard) 和容易 (Easy),解法分简单 (Simple) 和复杂 (Complex) 。

共识是 Hard,但是 Lamport 用他卓越的抽象能力做到了 Simple,这才是最顶级的研究,影响深远。相比之下,Hard and Complex 的工作就显得稍微差一些了,主要是太难被人理解;Easy and Simple 的工作也还可以,但是泛不起多少水花;最无法接受的就是 Easy but Complex 的研究了,然而感觉现在的绝大部分研究却都属于最后这种,令人失望。

欢迎关注一下我的 知乎账号,以后主要在知乎分享内容。感谢~
https://www.zhihu.com/people/ypjiang96/posts

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值