分布式与并发【三】浅谈分布式一致性算法Paxos

目录

  • 一、前言
  • 二、背景
  • 三、概念介绍
      • 提案(Proposal)
      • 角色(Role)
  • 四、算法推导
    • 从问题入手
    • 推导过程
          • 一、只有一个Acceptor
          • 二、多个Acceptor
        • 三、Proposer生成提案
          • 提案生成算法
        • 四、Acceptor接受提案
        • 五、Learners学习提案
      • 小结
  • 五、Paxos算法描述
        • 阶段一:Prepare
        • 阶段二:Accept
        • 阶段三:Sync
        • Paxos算法时序图
  • 六、总结

一、前言

说到分布式一致性算法,那么必然不可避免谈到Paxos,Paxos算法在分布式领域地位非常重要,接下来简单记录下Paxos算法的原理。由于个人水平有限,如有错误还请谅解,本文参考书籍《从Paxos到ZooKeeper》与一些网络博客。

二、背景

在分布式系统中,经常会发生例如网络异常、服务宕机等情况,为了解决出现问题时数据不一致而产生了Paxos算法,可以保证无论在任何情况下都不会破坏数据的一致性。

Paxos算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。

三、概念介绍

提案(Proposal)

每次在对数据进行更新时,由Proposer角色进行本次操作提案的发起,由Acceptor角色进行提案的同意表决,最终完成数据的更新。

提案的内容:提案编号和提案数据值Value,姑且暂时只关注Value(修改数据的值 = Value),后面会对提案的内容进行详细介绍。

角色(Role)

在Paxos算法中,将每个进程分配一种多种角色。分别为:

  • Proposer
    发起提案者,当发生数据更新时,由Proposer发起(propose)提案,提案的值value就是发生改变的值。
  • Acceptor
    提案的接受者,也是投票者,如果Acceptor接受(accept)了提案,那么该提案的值也将被选定(chosen)(超过半数以上的Acceptor接受同意提案后才会被选定)。
  • Learners
    提案的最终结果学习者,该角色不参与提案的选举,当提案最终被确定时同步数据,由Acceptor告知最终提案结果。

四、算法推导

从问题入手

如何保证当一个Proposer集合同时提出一个提案时,最终只有一个提案被选定,并且所有进程都能够学习到这个提案值,如果没有提案被提出就不会有提案被选定。

Paxos的目标:保证最终有一个value会被选定,当value被选定后,所有进程最终也能获取到被选定的value。

推导过程

一、只有一个Acceptor

如果在只有一个Acceptor角色的情况下,那么问题非常容易解决,只要Acceptor同意收到的第一个提案,该提案就被最终选定。

如下图所示,Acceptor只接受一个发起的提案,并最终选定提案1
分布式与并发【三】浅谈分布式一致性算法Paxos_第1张图片
虽然这种方式非常简单,但是会出现单点问题,当Acceptor节点宕机后,整个系统就无法工作了。为了避免单点问题,必须采用多Acceptor节点来进行选举提案。

二、多个Acceptor

如下图所示,如果多个Acceptor来接受提案,那么最终如何选定一个唯一的提案呢?
分布式与并发【三】浅谈分布式一致性算法Paxos_第2张图片
推导:
1、首先,我们要保证,当只有一个提案提出时,也能够被选定,那么得出

P1:一个Acceptor必须接受它收到的第一个提案。

2、为了解决上图的问题,提案1被Acceptor1接受,提案2被Acceptor2接受导致最终选定值不唯一,从而得出

一个提案被最终选定需要被半数以上的Acceptor同意选定

3、如果要满足第二点,那么也就意味着一个Acceptor能够接受多个提案,否则提案将无法被选定。在最开始我们认为一个提案的内容只包含被更新的数据value,这样看来只有value无法满足我们的要求。
这时,通过给提案加一个提案ID也叫提案编号来区分是哪一个提案,其中提案ID值按照提案的发起顺序单调递增,这里不解释具体如何生成提案ID。

提案内容由Value变更为【提案ID,Value】

4、由于允许了多个提案被选定,那么问题又来了,如果两个不同值的提案被选定,岂不是无法保证数据的一致性了吗?于是得出所有被选定的提案都有相同的value值。

P2:如果某个value为v的提案被选定了,那么每个编号更高的被选定提案的value必须也是v。

由于提案的选定由Acceptor来进行接受选定,那么可以将P2改为对Acceptor的约束P2a

P2a:如果某个value为v的提案被选定了,那么每个编号更高的被Acceptor接受的提案的value必须也是v。

只要满足P2a必然满足P2, 可得出P2a -> P2

5、此时感觉一切都很好,但是由于分布式系统中,网络波动和机器的宕机情况的发生,可能会出现数据不一致的情况。如下图所示:
分布式与并发【三】浅谈分布式一致性算法Paxos_第3张图片
其中Proposer1发生了网络异常或者宕机,此时Proposer2进行了提案【M1,V1】,此时系统中的超过半数的Acceptor选定了该提案,
然后Proposer1恢复了,将提案发给了Acceptor1,由于Acceptor没有接受过任何提案,根据之前 P1:一个Acceptor必须接受它收到的第一个提案,此时V2被选定,出现了数据不一致。并且违反了我们P2a的规定,此时V2 != V1。

6、为解决上面出现的问题,从而对P2a约束进一步进行强化,P2a是对Acceptor的约束,而P2b是对Proposer的约束

P2b:如果某个value为v的提案被选定了,那么之后任何Proposer提出的编号更高的提案的value必须也是v。

7、最终证明

P2c:如果一个提案【Mn,Vn】被提出,那么肯定存在一个超过半数已上的Acceptor集合S,满足以下两个条件中的任何一个。

  • S中不存在任何批准过编号小于Mn的提案Acceptor。
  • 选取S中所有Acceptor批准的编号小于Mn的提案,其中最大的那个提案Value值一定是Vn。

至此,只需要保证P2c,我们就可以保证P2b,从而保证P2和P1,最终保证数据的一致性。

三、Proposer生成提案

通过上面的推理,我们确定了,当一个新的提案被提出时,提案的value一定为当前被选定提案的Value值。那么Proposer如何能够满足这一点呢?

提案生成算法

步骤一(Prepare):
Proposer生成一个新的提案编号M,然后向半数已上的Acceptor集合发送请求,要求每个Acceptor做出ACK响应。

  • Acceptor保证不会接受比编号M小的提案
  • Acceptor如果已经接受过提案,那么将当前收到过提案中编号最大的那一个返回。

步骤二(Accept):
在收到所有Acceptor的响应后,生成一个提案【M,V】,其中V值由响应结果决定,如果没有Acceptor接受过提案,那么V值自己决定,如果有Acceptor接受过提案,则更新为所有返回提案中编号最大的V。

四、Acceptor接受提案

Acceptor可以忽略任何请求,包括prepare和accept。
Acceptor接受一个提案的条件为:

P1a:一个Acceptor只要尚未响应过任何编号大于M的Prepare请求,那么他就可以接受这个编号为M的提案。

最终可以得出,Acceptor只需要关心目前接受的最大编号的提案,小于该编号的不予以响应,另外关心当前已经响应的提案编号,保证不接受任何小于已经响应提案编号的提案。

五、Learners学习提案

当最终提案被确定后,提案的值要被所有的Learners学习同步数据。
那么如何进行数据同步呢?

方案一
当Acceptor确定一个提案后,发送消息给所有的Learners,这样Learners可以第一时间获取到最新的值,但是需要所有Acceptor和Learners进行通信,通信次数为二者个数的乘积。

方案二
当Acceptor确定一个提案后,发送消息给Learners集合中的主Leader,然后由Leader和所有Learners通信,这样可减少通信次数,但是会出现Leader单点问题。

方案三
当Acceptor确定一个提案后,发送消息给Learners集合中的一个子集,然后由子集进行通信,这样是方案一与方案二的折中,既不会出现单点问题,也不会出现通信次数过多,子集的个数越多可靠性越好。但是网络通信复杂度较高

小结

至此,推导过程已经全部完成。我们来简单回顾一下:
1、一个提案的内容,需要包含提案编号M和值V
2、一个提案被选定需要超过半数的Acceptor集合接受
3、Proposer生成提案前先确定当前已经被接受的最大提案值
4、Acceptor不接受小于当前已经接受或响应的提案编号的提案
5、Learners数据同步

五、Paxos算法描述

通过上面的推导,应该已经了解了Paxos的执行过程。
接下来对Paxos算法流程简单描述一下,整个算法分为两个阶段

阶段一:Prepare

1、首先Proposer生成唯一的递增提案编号M,然后向半数已上的Acceptor发送Prepare请求,等待响应。

2、此时Acceptor判断,如果已经想响应过比M大的提案,则对当前提案不予以响应,否则返回已经响应过的提案中编号最大的那个,同时该Acceptor承诺不再接受任何编号小于M的提案。

3、如果Acceptor未超过半数响应,则重新发起提案,如果Acceptor响应内容含有提案,则选定所有返回提案中编号最大的那个,如果没有响应提案,则自己决定提案值V。

阶段二:Accept

1、向半数已上的Acceptor发送Accept请求,期望Acceptor接受提案。

2、如果Acceptor没有对编号大于M的其他提案做出响应,则接受该提案。

阶段三:Sync

如果一个提案超过半数已上的Acceptor接受,那么该提案被最终选定,并且Acceptor向Learners发送选定提案的消息,Learners进行数据同步。

Paxos算法时序图

分布式与并发【三】浅谈分布式一致性算法Paxos_第4张图片
至此,Paxos算法已经全部讲完了,其中可能有人发现了一个问题,那就是该算法活性无法保证,如果有两个提案依次进行提案请求,那么该算法将陷入死循环,无法最终选定一个Value,如图所示:
分布式与并发【三】浅谈分布式一致性算法Paxos_第5张图片
两个proposer依次提交prepare请求,都会执行响应成功,但是在提交accept请求时,都会被忽略,这样就会造成死循环,无法保证算法活性。

所以为避免这种问题,采用选举一个Leader Proposer来进行提案的提交,这样总有一个Proposer提交提案,就不会出现问题,当Leader Proposer挂掉时,再进行重新选举推选一个新的Leader避免单点问题。

六、总结

本文已经详细的总结了Paxos算法的推导过程和运行的时序调用流程。可能还有很多细节没有说清楚,请原谅水平有限。

本文中的图为个人所画,如果有引用,请麻烦标注参考本文地址。

参考资料
【1】《Paxos Made Simple》
【2】《从Paxos到ZooKeeper》
网络中部分博客。

你可能感兴趣的:(分布式与并发,java,算法,分布式)