分布式一致性——Paxos协议

105 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

Paxos协议

Paxos协议是一种基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一,其解决的问题就是在分布式系统中如何就某个值(决议)达成一致 。

Google Chubby的作者Mike Burrows说过:
这个世界上只有一种一致性算法,那就是Paxos。


Paxos协议的工作,就是让一群机器协同起来,使其在外部可以看成为一个完整的系统,举个栗子,某个节点获取到一条数据,我们必须要让另外的其他机器也能得知该数据的到来,进而使整个系统达到一个一致的状态。


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

Paxos算法详解

问题描述


假设有一组可以提出提案的进程集合,那么对于一个一致性算法来说需要保证以下几点:

  • 在这些被提出的提案中,只有一个会被选定
  • 如果没有提案被提出,那么就不会有被选定的提案
  • 当一个提案被选定后,进程可以获取被选定的提案信息

基本概念


在该算法中,有三种角色,我们用 Proposer(提出者)、Acceptor(决策者)、Learn(决策学习者)  来表示。而在真正的系统实现中,一个进程可能会充当不止一种角色,在这里我们暂且不关心进程是如何映射到各种角色的。\

  • Proposer:提案的提出者,可以有多个,会提出提案(value)。而我们所说的value,可以指任何我们想在该分布式系统中想要执行的操作,在一整个Paxos执行周期中,只会有一个value被批准。在一个提案中,不光包含value,还要有一个全局唯一编号n,且必须全局递增,例如可以考虑使用时间戳+serverID来实现,在这里不做过多讨论。
  • Acceptor:提案的决策者,会回应 Proposer 的提案,来接收和处理paxos的两个阶段的请求。
  • Learner:learner不会参与提案的决策,他会从 Proposer/Acceptor 学习最新的提案来达成一致。它的存在可用于扩展读性能,或者跨区域之间读操作。

协议的推导


推导的过程也许会有点晦涩难懂,直接看协议的过程也可以在没有失败和消息丢失的情况下,如果我们希望即使在只有一个提案被提出的情况下,仍然可以选出一个提案,这就暗示了如下的第一个需求。


P1:一个 Acceptor 必须批准它接受到的第一个提案。
在P1的基础上,我们还需要加上一个提案被选定需要由半数以上的 Acceptor 批准的需求暗示 Acceptor 可以批准不止一个提案。


不妨提出:
P2:假设编号为 N0、value值为 V0 的提案(即[N0, V0])被选定了,那么所有比编号 N0 更高的,且被选定的提案,其 Value 值也必须是 V0\

P2 可以视为一个较为宽泛的约定,我们可以对其进行一定的延伸:
P2a:假设编号为 N0、value值为 V0 的提案(即[N0, V0])被选定了,那么所有比编号 N0 高的,且被所有 Acceptor 批准的提案,其 value 值也必须是 V0


很明显满足 P2a 时 也一定能够满足 P2,因为只有首先被接受才能最终被 Acceptor 选定。
但 P2a 很可能因为 Acceptor 无法知道之前被选定的提案而无法实现,于是我们可以对其进行扩展:
P2b:如果一个提案 [N0, V0] 被选定后,那么之后 Proposer 产生的编号更高的提案,其 Value 值都是 N0


因为一个提案必须在被 Proposer 提出后才能被 Acceptor 批准,因此 P2b 包含了 P2a。更进一步:
P2c:对于任意的 编号n 和 Value v,如果提案 [n, v] 被提出,那么肯定存在一个由半数以上的 Acceptor 组成的集合 S,满足:

  • S 中不存在任何批准过编号小于 n 的提案的 Acceptor。
  • 选取 S 中所有的 Acceptor 批准的编号小于 n 的提案,其中编号最大的提案其 value 值为 v。
    \

Paxos 协议的流程


在上述的协议推导后,我们可以总结出 Paxos 算法拥有三个角色,且流程大致分为两步。\


阶段一
Proposer 选择一个提案编号 n,向半数以上的Acceptor 广播 Prepare(n)请求。\


并对于集合中的 Acceptor 需出如下回应:\

    • 若 n 大于该 Acceptor 已经响应过提案的最大编号,那么它作为响应返回给 Proposer ,并且将不再被允许接受任何小于 n 的提案。
    • 如果该 Acceptor 已经批准过提案,那么就向 Proposer 反馈当前已经批准过提案编号中最大但小于 n 的那个值


阶段二
如果 Proposer 收到来自半数以上的 Acceptor 对于其发出的编号为 n 的 Prepare 请求响应,那么它就会发送一个针对 [n, Vn] 提案的 Accept 请求给 Acceptor。
注意:Vn 就是收到的响应编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定
如果Acceptor收到一个针对编号为 n 的提案的Accept请求,只要该Acceptor没有对编号大于NPrepare请求做出过响应,它就接受该提案

Learner 如何获取提案\

    • 方案一
      当 Acceptor 批准了一个提案时,将将该提案发送给其所有的 Learner,该方案非常简单直接,但通信的次数最大会达到 M * N。
    • 方案二
      选定一个主 Learner,在 Acceptor 批准了一个提案时,就将该提案发送给主 Learner,主 Learner 再转发给其他Learner。
      虽然该方案较方案一通信次数大大减少,但很可能会出现单点故障问题。
    • 方案三
      将主 Learner 的范围扩大,即 Acceptor 可以将批准的提案发送给一个特定的 Learner 集合,然后集合中的 Learner 再转发通知其他 Learner。
      该方案可以提高可靠性,但也会增加其网络通信的复杂度。