TypechoJoeTheme

IT技术分享

统计

01. Paxos共识算法——分布式算法专题

2022-10-07
/
0 评论
/
1,696 阅读
/
正在检测是否收录...
10/07

一、相关论文

作者时间
Lamport1990未通过The Part-Time Parliament
Lampson1996How to Build a Highly Availability System using Consensus
Lampson1997Revisiting the PAXOS algorithm
Lamport1998正式发表The Part-Time Parliamen
Lamport2001Paxos Made Simple
Google2006关于Chubby的论文

二、算法组成

Paxos是一种算法,它包含两部分,其中一部分是核心算法;另一部分是基于核心算法扩展的完整算法

-核心算法完整算法
The Part-Time Parliament单法令议会(Single-Decree Synod)多法令国会(Multi-Decree Parliament)
Revisiting the PAXOS algorithmbasic-paxosmulti-paxos
Paxos Made Simple共识算法(Paxos Consensus Algorithm)实现一个状态机(Implementing a State Machine)

三、共识算法

3.1 共识问题

共识问题就是多个进程对一个值达成一致。

每个进程都可以提议(propose)一个自己想要的值,但是最终只有一个值会被选中,并且所有进程对这个选中的值达成一致。

共识问题中的值(value)可以是非常简单的,比如一个整型数字,也可以是任何非常复杂的信息。

3.1.1 两个要求

consensus算法有两个要求,即安全要求和存活要求。

  • 安全(safety)要求是指:

(1)只有一个被提议的值可能被选中。
(2)只有唯一的一个值被选中。
(3)只有一个值实际上已经被选中,一个进程才能学习到这个值。

  • 存活(liveness)要求是指:

(1)某个被提议的值最终一定会被选中,并且如果一个值被选中,那么一个进程最终能够学习到这个值。

3.1.2 三个角色

Paxos consensus算法中有3个角色,分别是

  • 提议者(proposer):负责提出一个值。
  • 接受者(acceptor):负责选择一个值(acceptor一般是奇数个)
  • 学习者(learner):负责学习到被选中的值。

这些角色是逻辑角色,它们完成算法中的不同功能。一个进程可以容纳多个角色

3.1.3 基本概念

  • 提议(proposal),提议是包含一个提议编号和一个值的值对。我们用{n,v}表示一个提议,其中n是提议编号;v是一个任意值。
例如{1,x}表示编号为1、要提议的值为x的一个提议

Basic Paxos算法分为两个过程,即选择一个值和学习一个值

  • 提议编号(proposal number)

这个提议编号是唯一且递增的,更准确地说,提议编号在一组进程中是全局唯一且递增的。

Lamport给出了一种简单且有效的方法来生成这个提议编号:每个进程都被分配一个唯一的进程标识(processid)(假设为32位),每个进程都维护一个计数器(counter)(假设为32位),每发出一个提议都把计算器加1。这个64位的组合值作为提议编号

3.2 共识过程

3.2.1 选择值过程

选择一个值的过程是Paxos consensus算法的核心,

而Paxos consensus算法又是Paxos算法的核心,所以选择值可谓是核心中的核心

阶段描述
1(a)一个proposer 选择一个提议编号 n,并且发送编号为n 的prepare 请求给大多数 acceptor。
1(b)如果-个acceptor 收到一个编号为n 的 prepare 请求,并日n 比它之前已经回复的任何 prepare 请求中的编号都大,那么它给出回复,承诺不再接受任何编号比n 小的提议,并且在回复中携带(如果有的话)已经接受的编号最大的提议
2(a)如果 proposer 收到大多数 acceptor 回复的一个其编号为 n 的prepare 请求,那么它给这些 acceptor 中的每个都发送一个accept 请求,这个请求携带一个编号为 n 的提议,提议的值是所有回复中编号最大的提议中的值,如果回复中没有提议,则这个值可以是任何值。
2(b)如果一个acceptor 收到一个编号为n的accept 请求,那么除非它已经回复了一个编号比n大的 prepare 请求,否则它会接受这个提议。

消息分解

阶段消息消息说明
1(a)prepare(n)这个消息携带一个参数n,n 是提议编号。简写成 PP(n)
1(b)promise(n, (n1,v})在Lamport 的描述中没有对 prepare 的回复消息起名,只是将这个消息称为 “对 prepar的回复”。这里将这个消息命名为 promise. promise 消息携带两个参数,即n和{n1, v}, 其中n是承诺的提议编号,{n1,v}是一个提议,这个提议的编号是n1,值是v。简写成 PM(n,{n1,v})
2(a)accept({n,v})这个消息携带一个参数口{n, v},{n, v}是一个提议,这个提议的编号是n,值是v。简写成 A({n,v})
2(b)无消息-

需要持久化存储的信息

持久化存储的信息名简写持有者说明
Tried Numbertnproposer上次使用过的提议编号,记成 p.tn,表示存储在 proposer中的 Tried Number
Promised Numberpnacceptor承诺过的提议编号,记成 a.pn,表示存储在 acceptor 中的Promised Number
Accepted Proposal{an,av}acceptor己接受的一个提议,提议编号是a,提议的值是 v,记成a.{an.av},表示存储在 acceptor 中的 Accepted Proposal

具体化过程

阶段具体描述
1(a)一个proposer生成一个新的提议编号n,n>p.tn,将n记录到p.tn中,并且发送prepare(n)消息给大多数 acceptor
1(b)如果一个 acceptor 收到一个prepare(n)消息,并且n>a.pn,则将n记录到a.pn 中。如果有接受过的提议a.{an,av},则回复 promise(n,a.{an,av})消息;如果没有,则回复 promise(n,null)消息
2(a)如果 proposer 收到大多数 acceptor 回复的承诺编号为n的promise 消息,则构建一个新的提议,提议编号是n,值v按下面的规则确定:(1)如果在所有这些 prornise 消息中有携带提议的,则用提议编号最大的提议中的值 (2)如果在所有这此 promise 消息中没有任何一个携带提议的,则可以用 proposer 自己要提议的值, 这个proposer 给这些 acceptor 中的每一个都发送一个 accept(n,v)消息
2(b)如果—个acceptor 收到accept(n.v)消息,且n>=a.pn. 则把a.{an.av}赋值成 {n.v}
  • 承诺(promise)一个提议编号就具体体现为将一个提议编号持久化存储
  • 接受(accept)一个提议就具体体现为将一个提议持久化存储。

综合消息与存储的具体化过程

阶段执行者收到消息执行条件持久化存储发送消息发送目标
1(a)proposer-n> p.tnp.tn=nPP(n)大多数 acceptor
1(b)acceptorPP(n)n> a.pna.pn=nPM(n,a.{an, av})发送消息的 proposer
2(a)proposerPM(n,{n1, v})从大多数acceptor 收到消息-A({n.v})1(a)中同一批大多数acceptor
2(b)accentorA({n.v})n >= a.pna.{an.av}= {n,v}--

3.2.2 progress保证

选择值过程的progress保证选择值的过程不能完全保证progress,也就是不能保证最终会达成共识。

举个活锁例子

proposer1发送给acceptor3的accept消息被proposer2发出的prepare消息取消,
proposer2发出的accept消息又被proposer1发出的第二轮prepare消息取消,
如此往复进行下去,存在永远都不会达成共识的可能,也就是出现了活锁。

为了避免出现这种情况,可以选出一个proposer,作为distinguished proposer

只有这个distinguished proposer才能发起提议,从而避免了活锁情况的发生

3.3 学习过程

3.3.1 学习值必要性

既然共识问题已经得到解决,那么Paxos算法还需要有学习值的过程吗?

当然需要,因为选择值的过程有两个问题没有得到解决:

  • 需要进行多次投票,所有进程才能达成一致。需要优化这个达成一致的过程,在有值被选中后,尽快让所有进程达成一致。
  • 更重要的一个问题是,即便所有进程已经对选中的值达成一致,进程也无法知道这个状态已经达到。
即便某个acceptor接受了多个提议,并且每个提议中的值都是同一个值,这个acceptor也不能确定这个值就是被选中的值。

3.3.2 学习值过程

阶段描述
1(a)当acceptor接受一个提议后,向所有的 learner 通知这个提议
1(b)如果 learner收到 acceptor 的通知,则接受这个提议。如果 learner 接受从大多数 acceptor 收到的某个提议,则这个learer 接受提议中的值
当learner接受(accept)一个值后,这个进程就知道这个值被选中了,如果所有进程都接受了一个值,那么所有进程也就达成了一致。

3.3.3 减少消息数量

每个接受提议的acceptor向每一个learner都发送一个learn消息,earn消息的数量是acceptor的数量与learner的数量的乘积。

为了减少learn消息的数量,可以指定一个learner作为distinguished learner,acceptor接受提议后,向distinguished learner发送learn消息,distinguished learner收到learn消息后,向其他learner发送learn消息

  • ALearn: 将acceptor发送给distinguished learner的learn消息称为ALearn消息, ALearn简写成AL
  • LLearn: 将distinguished learner发送给其他learner的消息称为LLearn消息, LLearn简写成LL
与distinguished proposer一样,distinguished leaner并不要求唯一,多个distinguished leaner并不影响Paxos算法的正确性。

具有distinguished leaner的学习值过程:

阶段描述
1将接受的提议通过 ALcarn 消息发送给 distinguished learner
2(a)distinguished learner 收到大多数 acccptor 的 ALcarn 消息后,接受提议中的值,并向其他 learner 发送 LLearn消息
2(b)其他 learner 收到 LLearn 消息后,接受这个值

综合消息与存储的具体化过程

阶段执行者收到消息执行条件持久化存储发送消息发送目标
1acceptor---AL({n,v})distinguished learner
2(a)distinguished learnerAL({n,v})从大多数 acceptor 收到消息-LL(v)其他所有 learner
2(b)learnerLL(v)----

承担distinguished learner角色的进程可能会发生宕机,因此可以指定多个learner作为distinguished learner,这样可以提高系统的可靠性,但同时也增加了通信成本,也就是有更多的消息发送。

一般只选择一个learner作为distinguished learner。

但是无论哪种学习值的过程,都不能保证消息丢失后仍然能够学习到最终的值。没有收到LLearn消息的进程可以重新发起一个提议,学习到最终的值。

3.4 两个过程合并

共识过程有2个阶段4个步骤,学习值的过程有2个阶段3个步骤

Paxos consensus算法是由这两个过程组合而成的。

在完整的Paxos consensus算法中,选择值的第4个步骤和学习值的第1个步骤是一个步骤,选择值的第4个步骤是这个步骤的前半部分,学习值的第1个步骤是这个步骤的后半部分

最终共识算法如下

阶段执行者收到消息执行条件持久化存储发送消息发送目标
1(a)proposer-n> p.tnp.tn=nPP(n)大多数 acceptor
1(b)acceptorPP(n)n> a.pna.pn=nPM(n,a.{an, av})发送消息的 proposer
2(a)proposerPM(n,{n1, v})从大多数acceptor 收到消息-A({n.v})1(a)中同一批大多数acceptor
2(b)accentorA({n,v})n >= a.pna.{an,av}= {n,v}AL({n,v})distinguished learner
3(a)distinguished learnerAL({n,v})从大多数 acceptor 收到消息-LL(v)其他所有 learner
3(b)learnerLL(v)----
Paxos consensus算法是一个三阶段算法

四、完整算法

Paxos consensus算法可以确定一个值,如果运行多个Paxos consensus算法,就可以确定多个值,将这些值排列成一个序列,这就是完整的Paxos算法。

Paxos完整算法,或者叫作Multi Paxos算法,就是多次运行Paxos consensus算法,形成多个实例的算法。

其中一种是独立实例运行的完整Paxos算法;另一种是只运行一次prepare消息的完整Paxos算法。

无论哪种方式,Paxos算法都会产生这样一个结果:

类似于每个进程都会形成一个数组,数组中的每个元素都是一个达成一致的值。

4.1 独立实例运行

4.1.1 算法简述

在Paxos consensus算法的消息中添加一个实例编号,也就是instanceid参数,简写成i

阶段执行者发送消息
1(a)proposerprepare(i,n)
1(b)acceptorpromise(i,n, {n1,v})
2(a)proposeraccept(i, {n,v})
2(b)acceptoralearn(i,{n,v})
3(a)distinguished learnerllearn(i.v)
3(b)learner-

添加了instanceid的Paxos consensus算法

阶段执行者收到消息执行条件持久化存储发送消息发送目标
1(a)proposer-n > p.tn[i]p.tn[i]=nPP(i, n)大多数 acceptor
1(b)acceptorPP(i,n)n> a.pn[i]a.pn[i]=nPM(i,n,a.{an, av}[i])发送消息的 proposer
2(a)proposerPM(n,{n1, v})从大多数acceptor 收到消息-A(i,{n.v})1(a)中同一批大多数acceptor
2(b)accentorA(i,{n,v})n >= a.pn[i]a.{an,av}[i]= {n,v}AL(i,{n,v})distinguished learner
3(a)distinguished learnerAL(i,{n,v})从大多数 acceptor 收到消息a.{an,av}[i]= {n,v}LL(i,v)其他所有 learner
3(b)learnerLL(i,v)----

4.1.2 脑裂处理

Paxos consensus算法会保证即便有多个进程认为自己是leader,也就是出现脑裂的行为,每个实例最终也只能有一个值被选中

4.1.3 空洞处理

在每个实例中,提议编号都是重新开始的,这也就意味着每个实例都是相互独立的。

一个leader可以同时发起对多个实例的值的提议。

也就是说,leader不必等第一个实例的值确定后,再开始第二个实例,各实例之间是可以并发执行的。

这可能会导致出现一种情况:如果在第一个实例的值的确定过程中出现消息丢失,或者某个消息延时,导致最终第一个实例的值没有确定,同时,leader开始了第二个实例,并且第二个实例的值成功确定,那么第一个实例的值是空的,而第二个实例的值是确定的。这种情况被称为空洞。

如果leader发现某个消息在一定的时间内没有得到回复,那么leader可以重发这个消息,重复的消息对Paxos的正确性是没有影响的。

通过重发机制,空洞最终会被填补上。

如果在leader重发消息前,另一个进程成为新的leader,则情况会有所不同。

  • 如果旧的leader的提议已经被接受,那么新的leader会继续保持这个提议;
  • 如果旧的leader的提议还没有被接受,则新的leader可以提议一个新的值。

不管怎样,这个空洞都会被填补上。

4.2 只运行一次prepare消息

在没有多个leader出现的情况下,每个实例都要经历3个阶段(其中有5个消息的传递),其值才能成功确定。

在有多个进程都认为自己是leader的情况下,实例都要经历超过3个阶段,使用更多的消息才能确定实例的值。

4.2.1 算法简述

分析前面所讲的独立实例运行的完整Paxos算法,可以总结出这样一条规律:

每个实例都是相互独立的,且从头开始编号运行,而且到第二阶段才开始提议具体的值,相当于每个实例的prepare阶段都是相同的,所以leader可以为所有的实例发送一个共同的prepare消息,也就是所有的实例共用第一阶段。

4.2.2 脑裂处理

每个进程成为leader后都会重新执行第一阶段,在第一阶段会重新生成一个新的提议编号,只要进程一直认为自己是leader,就会保持提议编号不变。

在第二阶段,如果accept消息失败,则进程要么放弃认为自己是leader,要么继续认为自己是leader,选择一个新的提议编号,重新执行第一阶段。

4.2.3 空洞处理

可以通过重试把空洞填补上。

如果有新的leader出现,则新的leader要么替旧的leader完成剩下的工作,也就是继续提议旧的leader要提议的值;要么提议一个新值。到底执行两者中的哪一个,要看新的leader在什么时间点执行prepare阶段。

五、算法应用于状态机

在实际中,Paxos算法的一种应用就是实现复制状态机

在Lamport的“Paxos Made Simple”论文中,就使用了“实现状态机”作为完整Paxos算法这部分的标题。

5.1 状态机概念

  • 状态机(state machine)

状态机是构建服务的一种常见方法。服务具有一个初始状态,服务接受动作(action),服务每接受一个action,内部状态就发生一次迁移,达到一个新的状态。

  • 确定状态机(deterministic state machine)

确定状态机中所有的命令都会产生确定的输出结果,并且把状态机带入一个确定的状态。

  • 复制状态机(replicated state machine)

为了达到高可用、故障容忍,服务冗余是常见的手段,采用状态机模式的服务,实现冗余的方式就是在多台机器上部署这个服务,也就是可以使用一组server,每一个server都独立部署一个状态机,每个状态机都会以相同的顺序执行所有客户端的命令

如果所有的server都执行相同序列的命令,那么它们会产生相同序列的状态和结果,让这些服务具有相同的状态。

我们称这样的状态机为复制确定状态机(replicated deterministic state machine),一般简称为复制状态机(replicated state machine)。复制状态机是分布式领域常用的一种技术。

5.2 状态机应用

客户端(client)连接server1,向server1发送命令,server1按照接收到的顺序在状态机上执行所有命令。

并且服务器之间存在一种机制——把server1上的所有命令复制到其他服务器上,其他服务器也会按照同样的顺序在自己的状态机上执行所有命令。

当server1发生宕机后,客户端可以转而连接server2,从server2的状态机上取得结果。

服务器之间的这种复制机制的实现方法有很多,可以采用Paxos算法,也可以采用Raft算法和Zab算法

5.3 Paxos实现复制状态机

为了保证所有的服务器都执行相同序列的状态机命令,我们实现一系列Paxos consensus算法的实例,其中第i个实例所选择的值就是第i个状态机的命令。

一台服务器被选出作为leader,客户端发送命令给leader,leader决定每一个命令应该出现在什么位置。

例如,如果leader决定某个客户端命令应该是第135个命令,那么leader会试图让这个命令被选成Paxos算法的第135个实例的值。

5.3.1 空洞处理

Paxos算法允许多个实例同时运行,这会导致空洞的出现,但是算法可以保证在后面的执行中把空洞填补上。

然而,这种填补仍然会影响复制状态机的执行,如果采用Paxos算法实现复制状态机,还需要对并发实例和空洞做进一步的处理。

因为状态机要按照顺序执行所有命令,所以我们可以采用最粗暴的串行方式,leader严格按顺序执行Paxos算法,也就是在没有确认上一个Paxos实例成功时,不开始执行下一个Paxos实例。

但是,即便严格按顺序执行Paxos算法,也仍然不能完全避免空洞的出现,因为空洞可能出现在非leader的进程上。

例如,在某个非leader的进程上,关于某个实例的所有消息都丢失了,而下一个实例的所有消息又都收到了,那么就会出现空洞。

所以,即便leader严格按顺序执行Paxos算法,非leader的进程也仍然需要一种机制处理空洞。

这种处理空洞的机制非常简单,就是所有进程都要严格按照命令序列执行每一个命令,如果在某个命令序列位置未发现值,也就是出现了空洞,则状态机不会继续执行,它会一直等待这个位置被填入值,即便在这个空洞之后的位置上有命令,状态机也不会继续执行。

在具体的算法实现中,对于上面这种情况,新的leader往往是向这个空洞里填入一个空操作命令,这个空操作是不会对状态机产生任何影响的,有了这个空操作,状态机就可以继续执行空洞后面的操作了。

另外,实例的并发执行也并不是没有任何条件,如果想要并发执行,所有命令之间就不能有任何关系。

比如后一个命令依赖前一个命令的成功执行,在这种情况下,最好还是严格按照顺序来执行Paxos实例。

六、 算法应用于原子广播

6.1 原子广播

原子广播也是一种非常常见的分布式技术

原子广播(atomic broadcast)协议用于把消息(message)向广播对象进行广播,并且保证消息能够被可靠地收到,且所有广播对象以相同的顺序收到。

6.1.1 原子广播的模型

原子广播可以用来构建分布式系统,这类分布式系统有很多不同的进程,如果其中某个进程希望广播一条消息,这条消息被抽象成一个值(value),其他进程能接收到这个值。

也可以说,这个值被投递(deliver)到其他进程上,或者说一个进程希望提交自己的值,并且能够同时接收其他进程提交的值。

原子广播可以被看作一个黑盒,这些进程通过这个黑盒完成值的提交和接收。我们将这些进程称为客户端(client)。

原子广播协议通常被定义为包含以下两个动作(action)原语(primitive)。

  • ABroadcast(v):广播动作,当客户端想广播值v时,它可以调用这个动作。
  • v=ADeliver():投递动作,客户端通过这个动作接收其他客户端提交的值。
这个动作一般是一个回调,当有值要被接收时,客户端会被回调。

6.1.2 原子广播的特性

从原子广播的定义可以看出,原子广播保证:如果有一个进程调用了广播动作(即ABroadcast),那么所有客户端的投递动作(即ADeliver)一定会被调用,并且调用ADeliver动作的顺序一定与调用ABroadcast动作的顺序相同。

6.2 Paxos实现复制状态机

我们可以使用Paxos算法来实现原子广播,当然,原子广播协议也可以不基于Paxos算法来实现。

与基于Paxos算法的复制状态机的实现,基于Paxos算法的原子广播的实现与其类似。

基于Paxos算法的原子广播也是通过执行一个Paxos consensus实例序列来实现的,每个实例都使用一个唯一且单调递增的编号来标识,这个编号被称为实例编号(instance identificator,iid)。

每个Paxos consensus实例都是一个要广播的值,Paxos consensus算法保证广播是原子的,所有客户端一定会收到同一个值。

按照实例的顺序投递值能保证全局有序

为了保证progress,也要保证在同一时刻最好只有一个proposer在向acceptor提交值。为了这个目的,把其中一个proposer认命为coordinator或者leader

七、业界应用

业界采用Paxos算法的实际案例有ChubbyMegaStoreSpannerDynamoDBS3ZooKeeper等。

  • Spanner:使用Paxos进行副本间的数据复制
朗读
赞 · 0
版权属于:

IT技术分享

本文链接:

https://idunso.com/archives/2902/(转载时请注明本文出处及文章链接)