天天看点

Paxos算法笔记

这篇文章是记录本人学习Paxos算法的理解,本人才疏学浅,如果有错误欢迎指正!转载请标明出处!谢谢😄

什么是Paxos?

Paxos是位于希腊的一个小岛🏝。

Paxos算法是由分布式系统大神 Leslie Lamport 提出的一种基于消息传递并且高度容错的一致性算法。Paxos算法的命名由来,是因为Lamport最初是用一个发生在Paxos岛上的城邦议会故事阐述的,可惜并没有人能听懂他的故事。Lamport很可惜没人理解他的幽默😞,于是重新写了一篇关于Paxos算法的文章Paxos Made Simple

为什么会有Paxos?

首先想想什么是分布式系统?🤔

通俗的来说,就是使用多台机器各司其职地为用户提供服务,而于用户而言,就像是使用单机系统一样方便,不需要也感受不到后端多台机器的运作😲。

那么分布式系统中需要解决的问题也就显而易见,就是如何使多台提供同一服务的机器协调一致?我们要知道,在一个分布式系统中,多台机器的地理位置可能不同(可能有的紧挨在一个机房,也可能有的横跨半个地球),每台机器的硬件性能不同,处理速度不同,实时网速不同,也能有的机器会宕机。

这个问题是使分布式系统正确运行的根本问题,也是分布式系统设计中的一大难题。而 Paxos 算法被誉为解决一致性问题的圣经👑。

Paxos怎么运作?

为了更准确地描述算法流程避免翻译带来的歧义,算法中重要的角色我会使用英文原文的名称

Paxos 算法是出了名的晦涩难懂😩,倒不是因为它包含什么定理公式,而是因为它的流程和规则较为复杂。但是所谓难者不会,会者不难。只要理清它的执行过程,就会自然清晰明了。

为了更加清晰地解释,我们将 Paxos 分为两类:

  • Basic Paxos:一个或多个机器提出指令,仅有一个指令会被选中
  • Multi-Paxos:由多个 Basic Paxos 共同组成,用于确定一系列指令

下文将详细讨论选定单个 value 一致性的 Basic Paxos 过程,明白 Basic Paxos 后,其他的 Paxos 形式就迎刃而解了。

Basic Paxos

在阐述 Basic Paxos 过程之前,我们需要知道其包含两个角色:

  • Proposers:
    • 处理 client 请求
    • 提出一个 proposal
  • Acceptors:
    • 处理 proposers 请求
    • 回复当前 proposal 的投票情况
    • 保存已经选定的 proposal 和当前的投票状态

在Paxos Made Simple原文中还有第三个角色Learner,它的作用仅仅是获取已被选中的proposal,在此为了方便表述将它与Acceptor合为一谈

在执行Paxos算法的过程中,任何一个机器都可以同时充当其中的任意一个或多个角色

从实例出发

现在假设这样一个记录指令日志的应用场景:

我们的分布式系统中有一个服务需要由一个集群提供支持,这个集群包含五台 Server。

任意时刻都可能有某个 Client 向其中一台 Server 发出指令请求。

Paxos算法笔记

那么,当五台 Server 收到不同的 Client 请求时就会执行 Paxos 算法来确保整个集群的一致性,保证每台 Server 中的指令日志内容及顺序完全一致。这样一来,才可以保证每台 Server 按照指令日志执行完指令以后集群中的数据完全一致。

每个收到 Client 指令请求的 Server 在算法中就充当 Proposer 的角色(记住哦!Server 可以同时是 Proposer 和 Acceptor)。Client 将指令请求交给 Proposer 后就不用再管了😴,接下来就是 Server 之间协调一致的工作,完成一致性工作后 Proposer 会把执行后的结果返回给 Client。

接下来,我们详细讨论一致性工作是怎么完成的?

请求这么多,听谁的?

Basic Paxos 最终只会选定一个指令,那么这多么的指令请求,听谁的呢?

Proposer 会将 Client 的请求作为一个 value 包装成一个 proposal 发送给所有的 Acceptor,由 Acceptor 来做仲裁。

Paxos 是一个民主的算法,遵循少数服从多数的原则。当超过半数的 Acceptor 都 accept 了某个 proposal 时,这个 proposal 中的 value 就会被唯一选定,且不会再改变(划重点!!!)

有了这样一个原则,所以通常会选出奇数个 Acceptor。因为根据半数原则,如果选出偶数个 Acceptor,必然会浪费一个 Acceptor 的资源(例如选择 4 个,半数是 2;选择 3 个,半数也是 2,而只要有超过 2 个 Acceptor 的投票相同就可以确定答案)

那么只选 1 个 Acceptor 行不行呢?

Paxos算法笔记

乍一看貌似可行,但是,如果这个唯一的 Acceptor 宕机了怎么办?那么整个算法就无法执行下去了,这违背了高容错的原则,因此尽量选择 3 个及以上的 Acceptor

为了方便讲述,这里我们假设每个 Server 即是 Proposer 也是 Acceptor

还有一点要注意的是,因为只有超过半数的 Acceptor 投票后,才会选定一个 value,因此,在选定之前 Acceptor 需要暂时记录自己当前已投票的 proposal,即在 choose 阶段前,还需要有一个 accept 阶段

接下来,又有了一个新的问题:Acceptor 到底应该 accept 哪个 proposal?

accept 并且只 accept 第一个收到的 proposal?

如果我们让 Acceptor 只 accept 第一个收到的 proposal,行不行呢?来看这样一种情况:

Paxos算法笔记

可见,在这样的情况下,没有一个 proposal 的票数占据半数以上,形成了死锁

由此,我们可以提出改进,Acceptor 也许应该可以 accept 多个 proposal

accept 所有收到的 proposal?

这个方案是否可行,可以看这样一种情况:

Paxos算法笔记

在这种情况中,集群最终会选定两个 value,这违背了只能选定一个 value 的原则

仔细分析上面的情况,可以发现,在 Server5 提出 proposal(mov) 之前,集群其实已经有一个 proposal(add) 票数过半,成功选定。

因此,在一个 Proposer 提出 proposal 之前,应当先检查是否已经有一个选定的 value,如果有了就不再提出新的 value 了,这种先检查后提交的方式,正是 2PC

2PC 能够胜任吗?

至此,我们似乎已经做足了准备,可惜,这样的系统还是存在缺陷,如下:

Paxos算法笔记

在这种情况中,Server1 和 Server5 在提出 value 前都进行了检查,确实没有被选中的 value,可是因为 Acceptor 在 accept proposal 时存在网络延时,导致还是出现了选定两个 value 的情况出现,因此我们必须要对proposal进行排序,以拒绝旧的proposal (或是优先级低的)

比如在上面的例子中,Server3应该 reject Server1,因为Server5更新

独一无二的 proposal 和喜新厌旧的 Acceptor

为了能够对 proposal 排序,我们需要为每一个 proposal 设置一个唯一的id来标记它。并且规定:

  • id更高的 proposal 较 id 更低的 proposal 享有更高的优先权
  • proposer 能够选择一个比它见过或用过的 id 更高的 id 作为自己的 proposal_Id

为了保证 proposal_Id 能够保持递增且唯一,有两种实现方法:

  • RoundNumber + ServerId:因为算法可能需要多次循环才可以最终选定 value,因此每个 Server 都必须持久化保存它所见过的最大的轮次数,这样保证 id 递增,再连接 ServerId 保证唯一性。RoundNumber需要持久化保存,以保证宕机后也能快速恢复
  • TimeStamp + ServerId:使用系统TimeStamp + ServerId也可以保证递增性和唯一性,但是需要注意保证系统时间的一致

现在,Proposer 提出 proposal 的格式就是 <proposal_Id, value>

万事具备,开始执行

至此我们知道了 Basic Paxos 算法种包含的各种角色,考虑并解决了种种可能出错的情况,接下来通过一张图,疏通一下 Basic Paxos 整体运行流程:

Paxos算法笔记

为保证算法正确运行,Acceptors必须将 minProposal, acceptedProposal, acceptedValue持久化保存到本地

三个 Basic Paxos 实例

prepare 开始时已有 value 被选定

如果本轮算法执行过程中,已经有一个 value 被选定,那么后续的 Proposer 在发起 proposal 前会检查到这个结果,并且会将自己的 value 替换为已选定的 value(参考上小节步骤4)

Paxos算法笔记

已有 value 被 accepted 但是还未选定,新的 Proposer 检查到了

若在一个 Accepter 中,已经存在一个 accepted value 但是这个 value 还未被选定,而此时又收到了新的 Proposer 发了的 perpare 请求,那么这个新的 Proposer 会将自己的 value 替换为 accepted value

Paxos算法笔记

已有 value 被 accepted 但是还未选定,新的 Proposer 没有检查到

在上述的情况中,若新的 Proposer 没有检查到 accepted value,则它会继续使用自己的 value,而根据“喜新厌旧”的原则,先前的 accepted value 在后续的 Accept 阶段中会被拒绝

Paxos算法笔记

还有几点要补充

我们先看这样一种情况:

Paxos算法笔记

在这个情况中,两个 Proposer 在发起 proposal 时都未检查到对方的存在,导致活锁

解决方法:每个 Proposer 一轮 proposal 失败后,随机延时一段时间再重发 proposal,给其他 Proposer 成功的机会

我们还要注意的是:

  • Paxos 算法默认集群中没有坏消息或恶意消息,即应用在non- Byzantine fault情况下
  • Proposer 的最终目的并不是全力是自己的初始 value 被选定,而是全力是集群尽快达成一致
  • Accepter不会知道最终选定的 value 是什么,只有 Proposer 才知道,所以如果一个 Server 想知道最终被选定的 value ,它必须以 Proposer 的身份发起一次 proposal(value被选定后就唯一确定,不再更改,所以以获得选定 value 为目的的 proposal 可以携带任意 value)

参考资料

[1] Paxos lecture (Raft user study),Diego Ongaro

[2] Paxos Made Simple

[3] 知行学社——Paxos

继续阅读