paxos协议学习

1. paxos算法历史拾遗

  Paxos由Lamport于1998年在《The Part-Time Parliament》论文中首次公开,最初的描述使用希腊的一个小岛Paxos作为比喻,描述了Paxos小岛中通过决议的流程,并以此命名这个算法,但是这个描述理解起来比较有挑战性。后来在2001年,Lamport觉得同行不能理解他的幽默感,于是重新发表了朴实的算法描述版本 Paxos Made Simple ,而且使其获得2013年图灵奖。

  自Paxos问世以来就持续垄断了分布式一致性算法,Paxos这个名词几乎等同于分布式一致性。Google的很多大型分布式系统都采用了Paxos算法来解决分布式一致性问题,如Chubby、Megastore以及Spanner等。开源的ZooKeeper,以及MySQL 5.7推出的用来取代传统的主从复制的MySQL Group Replication等纷纷采用Paxos算法解决分布式一致性问题。

  但是paxos算法的描述最开始只是给出了单阶段的提交数据在分布式系统的一致性,只是对一次选举进行裁决保证分布式一致性,但是对于连续的多阶段行为描述的不够清楚,所以大家很难和具体的场景对应起来,理解起来也是云里雾里,得亏这个算法是大佬发表的,所以有那么多人愿意去思考和尝试应用,估计要是一个名不见经传的毛头小子提出来的是不是立马就被扔到墙角的垃圾桶里面取了,写的啥玩意儿啊😢。结合自己学习到的知识也尝试对paxos做一个小小的笔记,可能理解的也会有一些疏漏,文末我也会把自己参考和学习用到的资料都列一下,使大家更方便的学习协议的真面目。

  实现一致性算法的系统,例如 Chubby,ZooKeeper 和 Spanner。对于 Chubby 和 Spanner 的算法并没有公开发表其技术细节,尽管他们都声称是基于 Paxos 的。ZooKeeper 的算法细节已经发表,但是和 Paxos 着实有着很大的差别。

当我第一次学习paxos的时候真的是这种感觉:
在这里插入图片描述

paxos 是属于单阶段的裁决系统,这一句非常重要。

2. 从分布式算法开始

1. 分布式算法是做什么用的

  想要了解分布式算法的是做什么的,先要理解什么是分布式。分布式本身是为了让我们的存储服务能够达到更高的可用性,即使有少量的节点挂掉也不会影响我们的整体对外的服务的可用性。像aws的对象存储可靠性要求是9~13个9. 而这么高的可靠性都是建立在可靠性没那么高的硬件上的,如果使用单个节点来保证这个可靠性那真的是太难了,硬件老化,服务升级,电力不足等等各种意想不到的因素,根本无法使用单点来达到这种可靠性,但是使用分布式的话则是相对很轻松的达到要求。就像使用elasticsearch的集群一样,正常情况下只要同时挂掉的节点数量小于集群总数一半,那么整个集群仍然可以正常的堆外提供服务。假如有101个节点,那么可以容忍50个挂掉,想想这种容错能力,已经非常不错了(elasticsearch的举例并不完全正确,具体和使用的配置等都有关系,但是基本上是这个意思)。
  那么分布式系统如果想要无差别的对外提供服务,首先要满足数据必须是一致的,也就是挂掉一部分节点之前和之后同样的方式读出来的数据必须是一致的,所以分布式系统的一致性问题最终都归结为分布式存储的一致性,分布式算法就是为了解决分布式存储的一致性问题。也就是分布式系统著名的CAP理论中的C(一致性)。

2. 分布式数据一致性的类型

为了提高服务的可用性,很多都会采用分布式的算法来让服务提供更高的可用性,在实际中也有很多案例。

1. backup

定期备份,在早起的运维中,有可能一个主要的工作就是对数据库进行备份,防止数据完全丢失,这种备份对在线服务一般影响较小,但是数据的完整性是受质疑的,只能在节点宕机时勉强恢复,当然这种实际上算不上是分布式的。

2. 主从异步复制

  采用这种方式最常见的就是mysql了,mysql常见的方式是使用master/slave架构,这种也是分布式最基础最简单的一种架构。对mysql的写操作都必须由master处理,读操作可以走master也可走slave,对master的写操作都会通过异步的binlog同步到slave,由于网络,数据吞吐量比较大等原因,有可能通过master写入或者修改的数据需要经历数分钟或者更久的时间才能在slave完成同步,这样的话数据的可见性会受到影响,比如可能通过master查出来的数据和slave查出来的不一样,如果是读写分离,可能出现用户明明已经写进去了再查找(查从库slave)的时候却发现没有的问题。

3. 主从同步复制

  这个其实就是2pc协议,跟主从异步复制相比, 主从同步复制提供了完整的可靠性: 直到数据真的安全的复制到全部的机器上之后, master才告知客户端数据已经安全。
但主从同步复制有个致命的缺点就是整个系统中有任何一个机器宕机, 写入就进行不下去了(无论是master还是slave)。相当于系统的可用性随着副本数量指数降低(副本数越多,可用性越低)。

4. 半同步复制

  在同步和异步之间, 做一个折中, 看起来是一个不错的方案.。这就是半同步复制。读写都通过master, 它要求master在应答客户端之前必须把数据复制到足够多的机器上, 但不需要是全部。 这样副本数够多可以提供比较高的可靠性; 1台(或者少量)机器宕机也不会让整个系统停止写入。
  但是这时候面临的一个问题是:如果master挂掉了,那么如何选出一个拥有最全数据的slave来作为master,需要解决如何找出那个已经包含最全的数据的node,同时,这个算法还面临的一个问题是只能通过master读写,系统的吞吐量可能较低(但是实际上kafka就是类似这个模式,而且吞吐量惊人,哈哈哈)

5. 分布式一致性协议

  这里的分布式协议包括一些比较原始的探讨性的分布式协议,比如2pc,3pc这些只能在极为苛刻的条件下完成分布式一致性的协议(其实算不上真正的分布式协议,因为他打破了分布式一致性协议的假设条件),同时还有比较完整的分布式一致性协议,比如paxos,raft,pacificA协议,这其中分布式协议的始祖应该就是paxos协议了。每个分布式的协议不太一致,但是又有一些相似的地方。本篇主要介绍paxos协议。

3. paxos协议简介

  在正式开始介绍paxos协议之前,一定要牢记一个概念就是,paxos是单阶段协议,用人话来说就是针对单次写入达到在大多数节点的数据一致性的协议。后面会进一步举例说明这个概念以及可能发生的场景。

1. paxos的假设

  在讨论协议的时候一定要先弄清楚边界和前提条件,要不然后面在讨论可用性的时候可能有些假设场景本身就是不合理的,这样也会导致对算法的理解出现偏差。paxos算法中解决了如何在不可靠硬件基础上构建一个可靠的分布式系统的方法。但paxos核心算法中只解决网络延迟/消息丢失(节点不可达)/乱序的问题, 它不试图解决存储不可靠和消息错误的问题(拜占庭问题), 因为这两类问题本质上跟分布式关系不大, 属于数据校验层面的事情。这里的假设是,如果正常情况下数据在节点a可以实现存储,那么他应该在节点b上面也能够实现存储。
具体的假设有如下几个方面

1. 节点层面
  1. 每个节点处理和运行的speed是任意的
  2. 节点可能会出现failure(这个failure是指节点的failure不是指消息处理失败)
  3. 出现failure的节点在恢复后如果storage是ok的,可能会重新加入到paxos 协议的处理流程中去
  4. node不会出现拜占庭问题(不会故意伪造虚假消息)
  5. 在一个节点上能够成功处理并存储的消息,在其他节点上也是可以的
2. network层面
  1. 任意两个节点之间可以进行通信(在没有发生网络分区等的正常情况下)
  2. 节点之间的消息发送是异步的,消息传送经历的时间也可能是任意的
  3. 消息有可能lost,也有可能乱序,还有可能重复
  4. 消息不会变成拜占庭消息
3. Lamport的举例

  为描述Paxos算法,Lamport虚拟了一个叫做Paxos的希腊城邦,这个岛按照议会民主制的政治模式制订法律,但是没有人愿意将自己的全部时间和精力放在这种事情上。所以无论是议员,议长或者传递纸条的服务员都不能承诺别人需要时一定会出现,也无法承诺批准决议或者传递消息的时间。但是这里假设没有拜占庭将军问题(Byzantine failure,即虽然有可能一个消息被传递了两次,但是绝对不会出现错误的消息);只要等待足够的时间,消息就会被传到。另外,Paxos岛上的议员是不会反对其他议员提出的决议的。

2. paxos协议中的相关概念

1. 角色:
  1. client: 请求的发起方
  2. proposer: 提议者
  3. acceptor: 接受者,也就是接受提案的裁决人
  4. learner:不参与决策,从Proposers/Acceptors学习最新达成一致的提案的value进行持久化,并应答给client
2. 其他术语
  • paxos instance: paxos实例,就是对某个提案进行决策的过程,可能包含一个或者多个round
  • round: 一次提案投票的过程(一个round并不能完全决定一个value是否被选中,一个round包括一个prepare phase+一个 accenpt phase)
  • prepare phase: 预选阶段
  • accept phase: 投票阶段
  • proposal number : 请求时携带的提案编号,对于每个proposer就是他们在发起每个round的编号
  • proposal value: 在proposer在第二阶段向accepter发起请求时使用的proposal的value,这个就是要整个分布式集群在当前阶段要达成一直的数值
  • proposal: 提案,是在round的第二阶段accept phase生成的,包含一个proposal number,一个value, 由- - proposal发送给各个acceptor
  • quorum : 在大部分场景里都是指多数派, 也就是半数以上的Acceptor
3. acceptor的一些概念
  • last_pnum: 是Acceptor在阶段一种的prepare request最大的proposal number
  • last_prop: acceptor在第二阶段accept的最新的proposal,他包含了一个proposal number和一个proposal value

4. paxos协议过程描述

1. 数据的决策分为两个阶段:

1. 预选阶段, Prepare request阶段

这个阶段只是预选,并不会涉及到真正要存储的value值。

proposer

  1. 每个proposer会生成一个全局唯一的proposal-number,这个proposal-number理论上是要递增的,然后拿着这个proposal-number去半数以上的acceptor哪里进行prepare request,这个时候不需要携带提案内容,在下面的Accept阶段才需要提案内容value.

aceptor

  1. 如果这个proposal-number大于等于当前acceptor曾经接触到过的最大的prepare request携带的proposal-number(其他proposer在prepare request阶段带来的proposal-number)。
    1. 那么该acceptor会返回last_prop(上面有介绍),当然一个acceptor也有可能没有接收过任何阶段二的proposal,这个时候他返回的内容就没有last_prop 。
    2. 同时该acceptor会把当前prepare request的proposal-number保存下来,名字为last_pnum(见上文解释),同时作出一个承诺,不会在后面的第二阶段接受任何proposal-number<last_pnum的proposal。
  2. 如果这个proposal-number小于当前acceptor在prepare request阶段曾经接触到过的最大的proposal-number(其他proposer带来的proposal-number),那么该acceptor会返回错误信息,告诉proposer proposal-number信息过小,直接拒绝这次prepare-request

小结,在第一个阶段可以看出,在一个paxos实例运行的过程中,acceptor需要保存两个值,一个是在prepare request阶段接受过的最大值last_pnum, 一个是在accept request阶段接受过的proposal-number最大的proposal ,称为last_prop。
原文中是这样描述的

Phase 1. 
(a) A proposer selects a proposal number n and sends a prepare request with number n to a majority of acceptors.
(b) If an acceptor receives a prepare request with number n greater than that of any prepare request to which it has already responded, 
then it responds to the request with a promise not to accept any more proposals numbered less than n and with the highest-numbered proposal (if any) that it has accepted.


这里没有提到相等的情况,其实在原文中完全没有提到相等的时候应该如何处理,但是有这样的一段描述

Suppose an acceptor receives a prepare request numbered n, but it has already responded to a prepare request numbered greater than n, 
thereby promising not to accept any new proposal numbered n. 
There is then no reason for the acceptor to respond to the new prepare request, since it will not accept the proposal numbered n that the proposer wants to issue. 
So we have the acceptor ignore such a prepare request. We also have it ignore a prepare request for a proposal it has already accepted.

这个地方用的是greater不是greater or equal, 所以很难判断,但是根据paxos的假设,消息是可能duplicated的,那么prepare request消息可能会被重传,响应应该保持一致性,所以应该在等于的情况下应该是ok的。
当然实际上推论也是ok的,就是有可能造成更多的消耗。

2. accept request阶段,Accept阶段

提议者proposer在结束完prepare-request阶段后(proposer收到了半数以上的acceptor的响应,这里的响应是指http应答,不是要求所有的acceptor都对prepare request接受。如果连半数响应都收不到的话可能集群已经处于不可用的状态了,也有可能是proposer有问题)

propser

  1. 如果发现所有的acceptor回应的有拒绝的情况(说明自己的编号不够大),就会再回到预选阶段重新生成proposal-number然后进行prepare-request
  2. 如果没有acceptor拒绝,这个时候proposer会构建proposal来进行第二阶段的accept request, proposal的proposal-number就使用proposer当前的proposal-number,对应的value值有以下两种情况
    1. 如果有部分acceptor返回了proposal信息,则以返回的proposal-number号最大的那个proposal 的value值作为自己的value值
    2. 如果所有的acceptor在prepare request阶段都没有返回proposal,也就没有value值,那么当前proposal可以自行决定value值
  3. 选票proposal准备完之后就是proposer拿着这个proposal再向acceptor发起accept request请求(发送accept request和prepare request的目标acceptor并不一定要完全一样,但是要满足quorum个数)。

acceptor

  1. acceptor收到accept request之后,如果requst携带的proposal的proposal-number大于等于当前的acceptor保存的last_pnum,那么就会接受这个proposal,并回应ack
  2. 反之,如果requst携带的proposal的proposal-number小于当前的acceptor保存的last_pnum,那么就会拒绝该proposal

propser

  1. 如果proposer收到了quorum个数的acceptor的接受的response,那么久可以认为这个value是choosed了
  2. 如果没有收到quorum个数的acceptor的接受的response,那么proposer需要升级proposal-number,进入下一个round
3.可能出现的liveness问题

就是可能出现对应于一个paxos 实例(为了决定本次写入哪个值),经历了多个round仍然不能决定写入哪个值的情况
假如有两个proposer p1,p2

p1 使用1作为proposal发起了prepare-request
p2 使用2作为proposal发起了prepare-request
p1 使用(1,value)发起accept-request,被拒绝
p1 使用3作为proposal发起了prepare-request
p2 使用(2,value)发起accept-request,被拒绝
p2 使用4作为proposal发起了prepare-request
p1 使用(3,value)发起accept-request,被拒绝

这个过程可能循环下去,当然一般应该循环不了几轮,但是多余的循环是没有意义的,这就是活锁,如果经常陷入这种循环中则会是一个很大的无谓的消耗。
作者提供的方案,是先在多个proposal中选出来一个leader,每次的两个阶段都由这个leader来进行提交,这样的话就可以避免活性问题了。

2. learner如何获取到已经被choosed的value

learner需要在一个value被choosed之后(在上面的第二阶段被quorum数量的acceptor接受),能够将这个value持久化到本地,然后给client应答。
一种方式是每个acceptor在接受任何一个paxos-instance的value的时候会将这个消息发给所有的learner,但是这样的话如果learn比较多则信息量会非常的大。
另一种是所有的acceptor只把信息发给某一个固定的leader-learner,然后其他的learners再从这个leader-learn学习数据,但是这样的话如果leader-learner突然fail的话有可能会丢失一些数据(并没有丧失一致性,只是丢了一些数据),系统的可靠性会降低,分布式算法只是保持一致性(cap理论还记得么)
折中的做法是acceptor发送给部分learner(不是全部的learner),剩余的learners通过这些learners进行学习。

同时,因为消息丢失等原因,有可能一个value被choosed了,但是learner却不知道,这个时候,他可以通过请求acceptor来判断这个value是否被choosed了,但是因为acceptor有可能挂掉了一部分,所以也有可能出现learner访问了acceptor但是依然不知道某个proposal是否被choosed,这个时候learner不会轻易下决定,知道收到新的proposal被choosed,当然,learner也可以发起一个proposal(上面的两阶段请求)来完成一个新的proposal以决定哪个value被choosed。

5. 论文里面的一个实现案例

实现分布式系统的一种简单方法是:client的集合向中央服务器发出命令,服务器可以描述为确定性状态机,它以某种顺序执行client命令。状态机具有当前状态。它通过将命令作为输入并产生输出和新状态来执行步骤。
例如,分布式银行系统的client可能是柜员,并且状态机状态可能由所有用户的帐户余额组成。提款将通过执行状态机命令来执行,该命令将在且仅当余额大于提款金额时才减少帐户的余额,并产生旧余额和新余额作为输出。
如果单个中央服务器发生故障,则该服务器的实施将失败。因此,我们改为使用服务器的集合,每个服务器独立实现状态机。因为状态机是确定性的,所以如果所有服务器都执行相同的命令序列,则所有服务器将产生相同的状态序列和输出。然后,发出命令的client可以使用任何服务器为其生成的输出

为了确保所有服务器执行相同的状态机命令序列,我们实现了Paxos共识算法的不同实例序列,第i个实例选择的值是序列中的第i个状态机命令。每个服务器在算法的每个实例中扮演所有角色(proposer,acceptor和learner)。现在,我假设服务器组是固定的,因此共识算法的所有实例使用相同的各个角色。

在正常操作中,将选择一台服务器作为leader-proposer,在共识算法的所有实例中均充当杰出的proposer(尝试发布提议的唯一proposer)。client将命令发送给leader-proposer,leader-proposer决定每个命令应在执行序列中出现的位置。如果leader-proposer确定某个client命令应为第135个命令,则它将尝试选择该命令作为共识算法的第135个实例的值。通常会成功。它可能由于leader-proposer失败而失败,或者因为另一台服务器也认为自己是leader-proposer并且对第135条命令的含义有所不同而失败,但是共识算法确保最多可以选择一个命令作为第135个命令。

这种方法的效率的关键在于,在Paxos共识算法中,直到第2阶段才选择要提出的值。回想一下,在完成proposer算法的第1阶段之后,要么确定了要提出的值,要么proposer可以自由提出任何价值。
现在,我将描述Paxos状态机实现在正常操作期间如何工作。稍后,我将讨论可能出问题的地方。我考虑当前任leader-proposer刚刚失败并选择了新的leader-proposer时会发生什么。 (系统启动是一种特殊情况,其中尚未提出任何命令。)
新的leader-proposer,在共识算法的所有情况下都是learner,应该知道已经选择的大多数命令。假设它知道命令1–134、138和139,即在共识算法的实例1–134、138和139中选择的值。 (我们将在后面看到如何在命令序列中出现这样的间隙。)然后,它执行实例135-137和大于139的所有实例的阶段1。(我在下面描述如何做到这一点。)假设这些执行确定了在实例135和140中建议的值,但在所有其他的实例中没有给出建议值。leader-proposer接下来为实例135和140执行阶段2,从而选择命令135和140。

leader-proposer以及学习leader-proposer知道的所有命令的任何其他服务器现在可以执行命令1–135。 但是,它无法执行它也知道的命令138-140,因为尚未为命令136和137确定值。 虽然leader-proposer可以将client请求的接下来的两个命令作为命令136和137。但是,我们通过建议特殊的“no-op”命令作为命令136和137,使状态保持不变,从而立即填补了空白。 (它是通过执行共识算法实例136和137的阶段2来完成的。)

一旦执行了了这些无操作命令,就可以执行命令138-140。现在已确定了命令1–140。leader-proposer还为大于共识算法140的所有实例完成了第1阶段,并且可以在这些实例的第2阶段随意提出任何值。它将命令号141分配给client请求的下一个命令,并建议将其作为共识算法实例141的阶段2中的值。它提出了接收到的下一个client命令作为命令142,依此类推。

leader-proposer可以在获悉已选择其提议的命令141之前提出命令142。它在提议命令141中发送的所有消息都可能丢失,并且有可能在任何其他服务器了解leader-proposer作为命令141提出的建议之前选择命令142。当leader-proposer未能收到对其阶段2的预期响应时实例141中的消息,它将重新传输这些消息。如果一切顺利,将选择其建议的命令。但是,它可能首先失败,从而在选择的命令序列中留下空白。通常,假设leader-proposer可以提前获得α个命令-也就是说,在确定完成命令1至i之后,它可以提出命令i + 1至i +α。于是可能出现多达α-1个命令的间隙。

在上面的场景中,新选择的leader-proposer执行无限多个实例的第1阶段,比如实例135-137和大于139的所有实例。它可以对所有实例使用相同的proposal number,可以通过向其他服务器发送一条合理的短消息来做到这一点。在阶段1中,仅当acceptor已经从某个proposer那里接收到阶段2消息时,它才会回应一个内容较多的包含proposal的应答,否则只会以简单的OK做出响应 (对于实例135和140就是这种情况)。因此,服务器(充当acceptor)可以使用单个合理的短消息对所有实例进行响应。所以执行这些无限多个阶段1实例不会带来任何性能问题。

由于leader-proposer的失败和选举一位新leader-proposer是罕见的事件,因此执行状态机命令的主要成本(即,对命令/值达成共识)是仅执行共识算法第二阶段的成本。可以证明,Paxos共识算法的第2阶段在具有达成共识的任何算法的最小可能成本。因此,Paxos算法本质上是最佳的。

系统正常运行的话,只有一个leader-proposer,在当前leader-proposer失败与选举新leader-proposer之间会有ja短暂的暂停。
在异常情况下,leader-proposer选举可能会失败。如果没有服务器充当leader-proposer,则不会提出新的proposal来确定client的命令。如果多个服务器认为自己是leader-proposer,那么他们都可以在共识算法的同一实例中提出命令,这可能会导致无法选择任何命令。但是,安全性得以保留-两个不同的服务器将永远不会在选择作为第i个状态机命令的值上发生分歧(也就是在第i个实例中如果有两个server提出了不同的value,那么最终也只会存储一个)。选举一位leader-proposer是为了确保这个过程进展的更加顺利。

如果服务器组可以更改(作为sever端集群的server可能发生增减变化),则必须采用某种方法确定哪些服务器实现共识算法的哪些实例。最简单的方法是通过状态机本身。当前服务器集合可以成为状态的一部分,并且可以通过普通的状态机命令进行更改。我们可以让leader-proposer在执行完算法实例i后再执行α个命令,来指定接下来执行第(i +α)个算法实例的的服务器集。这是允许任意复杂的重新配置集群规模的算法的简单实现。

终于把作者是一个实现样例翻译完了,感谢google翻译的帮助😢,同时也感觉理解起来清楚多了。

顺带讲讲这里的一些新的发现:
最开始看paxos的时候,脑袋里面不自觉的就会跳出来leader挂了怎么办,脑袋里面一直闪现这个念头也阻碍了对paxos的理解,就像本文在介绍paxos的开始的地方说的一样,paxos协议的两个阶段实际上只是对client的单次存储请求如何在多个节点之间达到一致性的处理协议。所以他的两阶段行为只能是针对一次client请求,如何让多个节点的存储都达到一致性。自然而然的,也不包括leader挂了应该怎么处理这样的问题,对于这种问题需要其他的方式来满足。这应该也是paxos难以实现的地方,最开始没有paxos-simple这篇论文的时候,也没有这种例子,需要读者独立设计的地方还是太多了,所以只有和大佬一样牛的人才有机会去实践和设计这个算法。

在这篇文章里面,作者举例的时候刚好用的是一个连续操作的样例,也就是client多次提交的情况,还分析了在这个运行过程中假如leader-proposer挂掉需要怎样保持数据的完整性的情况。

这里的情况可能和client能够看到的一致性有一些差异,比如一个leader-proposal在执行一个client的command的时候失败了,那么可能会给client返回一个错误信息,假如这个command已经在部分acceptor上执行了accept reqeest, 新的leader-proposal在恢复后会重新把这个command重新执行一遍完成proposal,然后在learner上得到持久存储。

那么这样的话是不是需要执行故障转移才行,但是实行故障转移可能又是一个很大的话题了。但是从这里看,是可以保证server端的各个节点的数据是一致的。

同时,在多阶段执行的系统设计里面,作者引入了执行的sequence编号,也就是来自client的所有数据操作command都会被编号,而且是按照顺序执行,所有的learner中也要顺序的保存这个sequence,只有这样才能在leader-proposal发生意外的时候(这个时候需要对比和恢复多个阶段的数据)才能保持数据的一致性。

6. multi-paxos

通过上面的案例我们也可以看出来,如果只有一个leader-proposal执行的情况下,不会出现活锁的情况,而且可以忽略prepare request,因为只有一个leader-proposal,所以不会发生重复的情况
在multi-paxos中,正常情况下只有一个阶段二执行,在出现冲突,有多个节点认为自己是leader-proposal的时候就退回basic-paxos模式,直到选出leader-proposal之后再进入multi-paxos模式。

为了实现这一点,round(轮数)I被包括在每轮的accept request中,对于同一个Leader,每次accept request会顺序加1。Multi-Paxos将无故障消息延迟(学习建议)从4延迟减少到2延迟

可以参考下面wiki百科的图片理解

同时还了解到base-paxos有一定几率形成活锁,所以实际上使用的都是multi-paxos

这里举的是master选举,并不明显,因为master节点不会经常换,但如果是日志数据的提交,那么久难以接受了,因为日志提交操作是一个很频繁的动作,如果总是产生活锁,那吞吐量太低了。
所以才有muli-paxos,muli-paxos主要是在正常情况下限定了只有一个node会参与master选举,这样就肯定不会产生活锁问题,只有这个master挂了才会进行一次真正的basic-paxos。
对应的每条日志只有一个master node去进行日志的提交,这样就快很多了。

看来看去,总感觉paxos,raft,zab,pacificA都很类似的感觉😢

7. 参考wiki上的流程图表示

1. basic poxos相关

1.basic paxos 没有failure的运行状态

Client   Proposer      Acceptor     Learner
   |         |          |  |  |       |  |
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(1)
   |         |<---------X--X--X       |  |  Promise(1,{Va,Vb,Vc})
   |         X--------->|->|->|       |  |  Accept!(1,V)
   |         |<---------X--X--X------>|->|  Accepted(1,V)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

  1. basic paxos当一个acceptor失败的情况
Client   Proposer      Acceptor     Learner
   |         |          |  |  |       |  |
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(1)
   |         |          |  |  !       |  |  !! FAIL !!
   |         |<---------X--X          |  |  Promise(1,{Va, Vb, null})
   |         X--------->|->|          |  |  Accept!(1,V)
   |         |<---------X--X--------->|->|  Accepted(1,V)
   |<---------------------------------X--X  Response
   |         |          |  |          |  |
  1. 当冗余的learner fail,依然会成功
Client Proposer         Acceptor     Learner
   |         |          |  |  |       |  |
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(1)
   |         |<---------X--X--X       |  |  Promise(1,{Va,Vb,Vc})
   |         X--------->|->|->|       |  |  Accept!(1,V)
   |         |<---------X--X--X------>|->|  Accepted(1,V)
   |         |          |  |  |       |  !  !! FAIL !!
   |<---------------------------------X     Response
   |         |          |  |  |       |
  1. 当proposer fail
    在这种情况下,proposer在提出一个值之后,但在达成协议之前失败。具体地说,它在第二阶段失败,只有一个acceptor接收该值。然后,一个新的leader-proposer(提名者)被选出(但图中没有详细显示)。注意,在这种情况下新的leader-proposer选出后会在进行一次parepare+accept,总共会有两轮(轮从上到下垂直进行)。

思考一下,假如这个时候如果proposer在第一个阶段就失败了,那么这个值应该就是没有了,需要给client返回fail

Client  Proposer        Acceptor     Learner
   |      |             |  |  |       |  |
   X----->|             |  |  |       |  |  Request
   |      X------------>|->|->|       |  |  Prepare(1)
   |      |<------------X--X--X       |  |  Promise(1,{Va, Vb, Vc})
   |      |             |  |  |       |  |
   |      |             |  |  |       |  |  !! Leader fails during broadcast !!
   |      X------------>|  |  |       |  |  Accept!(1,V)
   |      !             |  |  |       |  |
   |         |          |  |  |       |  |  !! NEW LEADER !!
   |         X--------->|->|->|       |  |  Prepare(2)
   |         |<---------X--X--X       |  |  Promise(2,{V, null, null})
   |         X--------->|->|->|       |  |  Accept!(2,V)
   |         |<---------X--X--X------>|->|  Accepted(2,V)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

  1. 多个proposer之间冲突的情况
Client   Proposer       Acceptor     Learner
   |      |             |  |  |       |  |
   X----->|             |  |  |       |  |  Request
   |      X------------>|->|->|       |  |  Prepare(1)
   |      |<------------X--X--X       |  |  Promise(1,{null,null,null})
   |      !             |  |  |       |  |  !! LEADER FAILS
   |         |          |  |  |       |  |  !! NEW LEADER (knows last number was 1)
   |         X--------->|->|->|       |  |  Prepare(2)
   |         |<---------X--X--X       |  |  Promise(2,{null,null,null})
   |      |  |          |  |  |       |  |  !! OLD LEADER recovers
   |      |  |          |  |  |       |  |  !! OLD LEADER tries 2, denied
   |      X------------>|->|->|       |  |  Prepare(2)
   |      |<------------X--X--X       |  |  Nack(2)
   |      |  |          |  |  |       |  |  !! OLD LEADER tries 3
   |      X------------>|->|->|       |  |  Prepare(3)
   |      |<------------X--X--X       |  |  Promise(3,{null,null,null})
   |      |  |          |  |  |       |  |  !! NEW LEADER proposes, denied
   |      |  X--------->|->|->|       |  |  Accept!(2,Va)
   |      |  |<---------X--X--X       |  |  Nack(3)
   |      |  |          |  |  |       |  |  !! NEW LEADER tries 4
   |      |  X--------->|->|->|       |  |  Prepare(4)
   |      |  |<---------X--X--X       |  |  Promise(4,{null,null,null})
   |      |  |          |  |  |       |  |  !! OLD LEADER proposes, denied
   |      X------------>|->|->|       |  |  Accept!(3,Vb)
   |      |<------------X--X--X       |  |  Nack(4)
   |      |  |          |  |  |       |  |  ... and so on ...

2. multi-paxos相关

1.下面是一个basic-paxos,multi-paxos包含多个basic paxos instance,
这个是multi-paxos的第一个请求,和basic-paxos基本一致,注意看下面的promise中多了一个I

Client   Proposer      Acceptor     Learner
   |         |          |  |  |       |  | --- First Request ---
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(N)
   |         |<---------X--X--X       |  |  Promise(N,I,{Va,Vb,Vc})
   |         X--------->|->|->|       |  |  Accept!(N,I,V)
   |         |<---------X--X--X------>|->|  Accepted(N,I,V)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

接下来会跳过phase1
Client   Proposer       Acceptor     Learner
   |         |          |  |  |       |  |  --- Following Requests ---
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Accept!(N,I+1,W)
   |         |<---------X--X--X------>|->|  Accepted(N,I+1,W)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

参考

老爷子的博客

wiki相关介绍

其他不错的博客01
02
03
04
05
06
07
08
09
10
11

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值