Paxos 分布式一致性协议算法简析

257 阅读8分钟

概览

Paxos 算法是一种用于在分布式系统中实现一致性的协议。它确保即使在部分节点出现故障或网络分区的情况下,系统中的多个节点也能达成一致的决策。Paxos要解决的问题就是要保证大多数节点对某个事项的值是什么达成一致,此时称这个值被选中,并且一旦选中就不会变化。

Paxos算法中有三种主要角色,每个角色在达成一致的过程中发挥不同的作用:

1. 提议者(Proposer)

提议者负责提出提案并发起共识过程。提议者生成提案编号和提案值,并向接受者发送请求,以获得多数接受者的承诺和接受。

2. 接受者(Acceptor)

接受者负责接收和投票提案,决定是否接受提案。接受者在收到提案请求后,根据提案编号和之前的承诺决定是否接受提案,并回复提议者。

3. 学习者(Learner)

学习者负责学习并记录最终达成共识的提案值。学习者从接受者接收到一致性的通知,更新本地状态,以反映达成的共识结果。

算法步骤

准备阶段(Prepare Phase):

  1. proposer:proposer通过发送一个带有提案编号的准备消息来启动共识过程,发送给acceptor。
  2. 提案编号:提案编号是当前提案的唯一标识符,有助于防止多个提案之间的冲突。
  3. acceptor检查:proposer在收到准备消息后,检查提案编号是否大于之前见过的任何提案编号。
  4. 承诺回复:如果提案编号较大,acceptor回复一个承诺,即不再接受任何编号更低的提案,并包含其已接受的最高编号的提案信息(如果有的话)。

承诺阶段(Promise Phase):

  1. acceptor回应:在收到准备消息后,acceptor向proposer发送承诺消息。
  2. 承诺消息内容:承诺消息包括acceptor的接受状态以及其已接受的最高编号的提案。
  3. 初次承诺:如果acceptor之前未接受任何提案,它会发出承诺但不包含任何已接受的值。
  4. 包含提案值:如果acceptor之前接受过提案,它会在承诺中包含提案编号和值。

接受阶段(Accept Phase):

  1. proposer收集承诺:如果proposer收到大多数接受者的承诺,它进入接受阶段。
  2. 发送接受消息:在接受阶段,proposer向acceptor发送带有相同提案编号和值的接受消息,要求其接受该提案,这个值可能是之前从acceptor返回的其接受过最大编号的提案的值,也可能是任意一个别的值。
  3. 验证提案编号:acceptor检查提案编号是否不小于其见过的任何提案编号。
  4. 接受提案:如果提案编号有效,acceptor接受提案并通知proposer。

学习阶段(Learn Phase):

学习阶段确保决定被可靠地传达给系统中的所有节点,包括learner(学习者)和proposer,以便分布式系统对选择的值达成一致。这一阶段完成了共识过程,并确保所有正确的节点最终拥有相同的一致值,保持分布式系统中的数据一致性。

Paxos 的关键性质:

  1. 安全性(Safety) :仅有一个值被达成一致。
  2. 活性(Liveness) :即使在存在故障和消息延迟的情况下,算法仍能继续推进。

流程图:

第一种场景:所有的acceptor此前都未曾接受过任何提案

准备阶段 && 承诺阶段:各proposer向超过半数的acceptor发送prepare消息,并收到承诺

step1:proposer1向 {acceptor1, acceptor2} 发送prepare消息

PAXOS Algorithm - step - 1.drawio.png

step2:proposer2向 {acceptor2, acceptor3} 发送prepare消息

PAXOS Algorithm - step - 2.drawio.png

接受阶段:收到prepare响应超过半数的proposer发起提案,并被acceptor判断是否批准

step1:proposer1向 {acceptor1, acceptor3} 发送accept消息

PAXOS Algorithm - 3.drawio.png

step2:proposer2向 {acceptor1, acceptor2} 发送accept消息

PAXOS Algorithm - step - 4.drawio.png

学习阶段

收集acceptor的接受消息,统计投票

提案投票数
[M1,V1]1
[M2,V2]2

最终提案M2被批准通过,并执行。

第二种场景:acceptor1此前已经接受过proposer1的提案[M1,V1]

此时proposer2发出prepare请求时,acceptor1会返回此前接受的最大编号的proposal的提案,即[M1,V1]给proposer2,随后,proposer2会发出[M2,V1]的提案,这是因为Paxos算法要求proposer在接收到acceptor返回的其之前accept过的最大的编号的提案后,proposer发出提案时选取的值必须得时这个最大编号的提案的值。

proposer2向 {acceptor1, acceptor2} 发送prepare消息 PAXOS Algorithm - proposer 2 step -1.png

proposer2向 {acceptor2, acceptor3} 发送accept消息

PAXOS Algorithm - proposer2 - step -2.drawio.png

收集acceptor的接受消息,统计投票(假设proposer1提出的提案[M1,V1]已被acceptor1和acceptor2接受)

提案投票数
[M1,V1]2
[M2,V1]2

此时虽然投票数一致,但是提案值都是V1,所有并不会导致做不出选择,不管选择哪个提案,提案的内容都是一样的。

注意:

  • 并不是等到所有的proposer或者acceptor完成了前一个阶段,然后再继续完成后面的阶段,举个例子,proposer1发起prepare消息后,如果收到的响应超过半数,则会发起真正的提案,并不会等到proposer2完成prepare的消息之后再进行。

  • 上述的流程也说明了一个acceptor可以接受多个提案,这样设计的好处是防止出现僵局,即不同编号不同值的提案的票数一样

  • Paxos算法允许多个提案被选中,但是要求被选中的提案的值是一样的(下面这句引用自论文Leslie Lamport的Paxos Made Simple,他是该算法的创造者)。

    We can allow multiple proposals to be chosen, but we must guarantee that all chosen proposals have the same value.

  • 节点可以在不同的时间点上扮演不同的角色,具体情况如下:

    • 一个节点可以成为提议者,发起一个新的提案。提议者生成提案编号和提案值,并向接受者发送准备(Prepare)请求。
    • 每个节点都可以是接受者。接受者接收到提案请求后,根据提案编号和之前的承诺决定是否接受提案。接受者可以同时作为提议者或学习者。
    • 学习者从接受者接收到最终达成的提案值,并更新其本地状态。所有节点都可以作为学习者,以确保系统的一致性。
  • 通过算法描述我们知道一个paxos算法实例只会选中一个值(如果一个acceptor已经接受过别的proposer的提案的值,那么别的proposer就可能就会使用这个值,如果其是acceptor已经接受过的最大提案编号的值),那么我们如果有多个值要被选中,该怎么办,一种是再构建若干个算法实例,这个实例是用来选中其他值的,当然这种做法一个是性能比较差,每一个instance都需要进行初始化以及上述的步骤,此外也无法保证顺序,拿事务日志的同步来说,每一个操作都用一个paxos instance同步的话,操作的顺序是无法保证的,Paxos算法并未对这种情况做什么保证。另外一种是使用改进后的Multi-Paxos算法,该算法适合用来处理多个值并保证顺序。

  • 为啥要求最少是大多数节点达成一致即可,原因也比较简单,当一个proposer发起一个提案并且被选中(即表示大多数acceptor已经accep了这个提案)后,后面又有一个proposer发起提案,为了保证一个Paxos实例中只允许一个值被选中,那么由于这个proposer在Prepare阶段会向大多数acceptor发起prepare request后,肯定会得到这个之前被选中的值,因为两个包含了大多数acceptor的集合,必然是有交集的。接着,这个proposer会拿着这个之前被选中的值作为提案的值去发起propose,即使最终被大多数acceptor接受这个请求,被选中的值还是这个值,没有违背Paxos一致性算法中关于只能有一个值被选中的规则。

关于Multi-Paxos算法

Multi-Paxos相对于Paxos算法最大的改进是能更高效地处理多个值(为做更明显的区分,把基础的Paxos算法称之为Basic Paxos),这主要依赖于一个leader proposer,在整个系统中,只有这个leader proposer才可以提出提案,并且在进行若干次成功的Basic Paxos算法流程之后,这个leader proposer便可以称之为是一个稳定的leader proposer。此时算法进入了极速模式(galloping mode),对后续的值,只需要使用一个proposer number做一次Prepare,之后便可以以相同的propose number对一后续的一系列值进行Propose操作,一直到有另一个proposer用更大的propose number发起Prepare(网络分区可能会导致某些节点与当前的leader proposer失去联系,进而会尝试选举自己为leader proposer)。通过省去了Prepare阶段,大大减少了在多个值的情况下的开销。

参考

  • <<Paxos Made Simple>> By Leslie Lamport
  • <<Multi-Paxos: An Implementation and Evaluation>> By Hao Du∗ and David J. St. Hilaire∗