深入解读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 的情况出现,因此在 Accept 阶段开始前,还需要一个 Prepare 阶段,对 proposal 进行检查

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

为了解决新的问题,我们必须再次做出改进。我们可以对 Acceptor 收到的 proposal 进行排序,并且只 accept 最新的 proposal,同时摒弃旧的 proposal(只是在 accept阶段,value 一旦 chosen 便不可更改)

为了能够对 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