zoukankan      html  css  js  c++  java
  • Paxos算法小结

    转自不正直的绅士,因百度空间迁移,无法注明出处,我从其google搜索引擎中的cache进行的copy.  

    不正直的绅士 是跟我一起工作过的非常有才的一个青年才俊。 

    Paxos的使用非常广泛。sanlock也使用了paxos

    共研究Paxos算法的程序猿参考。

    Paxos算法小结

    Paxos算法背景
    1.1 State Machine Approach与一致性算法
    1.2 CAP理论与一致性算法
    Paxos算法
    2.1 Paxos算法角色
    2.2 Paxos算法描述
    2.3 Paxos算法简单论证
    2.4 Paxos里两个阶段必要性
    3 Disk Paxos算法
    3.1 Disk Paxos算法正式描述
    3.2 Disk Paxos简单论证
    3.3 Disk PaxosPaxos对应关系
    3.4 Sanlock
    4 Google Chubby
    5 Amazon Dynamo
    6 Paxosd
    7 小结


    Paxos算法背景
    最近读了几篇关于Paxos算法和应用论文,整理思路一下以免遗忘。

    Paxos是一个分布式一致性算法,作用是保证整个集群对某件事情达成一致。它是一个相当简练算法,过却太好懂。很多介绍Paxos文章一上来就介绍算法原理、证明和应用,却没有好好谈谈它大背景,初次接触它人往往会落入各种细节,不得要领。我一开始看了Paxos之后,知道它是想解决什么问题,为什么要解决这种问题,和以前看过分布式系统又有什么联系。在初步了解过State Machine Approach,搞清Paxos在CAP分类中位置之后,才霍然开朗。

    1.1 State Machine Approach与一致性算法
    Paxos算法之前,应该先了解State Machine Approach,因为Paxos算法主要应就是实现一个分布式状态机。State Machine Approach是一种实现容错分布式系统一般性方法和模型。比如说,如果某个电子商务网站只有一台数据库服务器,那么这个数据库服务器崩溃之后,整个系统就能运行了,所以需要多台数据库服务器以便互相替换。问题是如何保持所有数据库服务器上数据都是一样呢?使用State Machine Approach,首先把每台数据库服务器看作一台状态机,它把用户请求作为输入,转换自身状态,再输出结果。比如账户余额为0,用户存入100,余额状态变为100,取出20,余额状态变为80。这种状态机有一个特点,就是确定性,只要给出每个输入和输入顺序一样,管计算多少次,输出和最终状态都一样。因此,只需要在每个节点上都创建一个状态机,设置相同初态,把用户输入都按同样顺序分发到这些状态机上,它们最终就能得到一样状态。如果有其中一台状态崩溃了,其他状态机还能继续响应服务,并在再崩溃机器恢复后,可以继续把错过输入再次在其上重放,它就又能同步到和大家一样状态。如果这台状态机因为磁盘损坏之类问题导致状态错误,那么和其他状态比对一下就能发现同,在一个有N个状态机集群里面,只要N/2 + 1台状态机正常,也就是多数状态机正常,就会丢失正确状态。常见数据库都有主从热备功能,主数据库日志会在从数据库上重放,主服务器秀逗时候,从服务器就顶上。用分布式状态机模型考虑一下这个例子,感觉十分贴切。

    State Machine Approach一个关键点是,分发到每台状态机每笔用户输入顺序都必须是一样,在有一个单点专门接受请求并复制分发时,是可以保证这点。但是我们喜欢单点,那么如何实现有多个节点(甚至每个节点)都可以接受在自同用户同请求,并且这些节点能对用户请求顺序达成一致呢?这就是一致性算法要解决问题。使用一致性算法,每个节点都可以发表提案,竞选第i号请求值,接着多数节点同意后,批准这个值,提案失败节点可以用自己刚才没通过值,继续竞选第i+1号请求。这样反复下去,每个节点依次把自己收到用户请求提交到集群批准,通过后就继续提案下一个用户请求,没通过就再次竞争下一个编号请求。一般数据库只能实现主从热备,用这种方式,就可以实现主主互备了。二阶段提交和三阶段提交都是这样一致性算法。但是一般算法比如二阶段提交,要求所有参与节点都通过了才能完成事务,任意节点失去联系都会导致整个事务阻塞。三阶段提交为事务设置了超时阶段,即使一段时间内没有来自Coordinator指示,也能自行提交事务,三阶段提交问题是无法应对网络分区。网络分区在一个线上系统实际运行中是很常见,比如交换机某个口坏了,或者路由器配错了,再或者网络拥塞导致时延变大,对网络应用来说,是无法区别只是时延变大了还是网络断了。分区发生时,数据中心整个网络暂时被分成几块互相能通信小网络,这个时候三阶段提交有可能在小网络里提交事务,导致一致出现。而且三阶段提交对每个事务都需要3次交互,时延太长。Paxos算法先进之处在于,当且仅当大多数成员达成一致时提交事务,即使网络被分区,每个分区内集群成员足整个集群多数时,就会提交事务,反之只要分区内节点能达到多数要求,系统就能继续运行下去。Paxos算法既会出现二阶段提交阻塞情况,也没有三阶段提交一致风险。

    1.2 CAP理论与一致性算法
    除了基于一致性算法分布式系统,还有其他类型吗?本节考虑一致性算法在CAP理论中分类。CAP是分布式领域著名理论,C代表Consistency一致性,A代表Availability可用性,P代表Partition Tolerance分区容忍性。CAP理论认为,一个分布式系统是可能同时满足一致性、可用性和分区容忍性。所谓一致性,就是某个变量值在整个集群中唯一性,比如在所有服务器上,账户余额X都为100,存在服务器A上X为200,服务器B上X为100情况,或者在外地朋友往我们账号里汇了100块,那么本地ATM机上再查寻必然能查到多了这笔转帐。CAP理论中可用性,和平时谈可用性太一样,这里可用性指是一个请求能在确定时间内得到服务器响应,无论响应内容是“请求执行成功”还是“请求执行失败”,都算数,只有服务器没有响应,或者请求被无限期阻塞,才说违背了可用性。分区容忍性指是在网络出现分区时候,集群上服务是否还能够正常运行。CAP理论成立理由很简单,假设有2台服务器,运行着主主互备数据库应用,A和B通过某种同步协议来保证一致性。假设某个时间点上网络出现故障(也即分区),这时用户向服务器A发送请求,更新自己账户余额,如果A接受更新,那么该用户余额在A和B服务器上出现一致,如果A阻塞用户请求,等待网络恢复并和B通信后才提交事务,那就满足可用性了,因为知道网络何时恢复,也许该请求永远被阻塞住了。

    典型CP系统例子就是是传统分布式数据库。在没有分区情况下,CP系统能够保证数据一致性,可以在任何一个节点写,再从其他节点读,数据必定是一致,出现分区时,通过把自己变得可用以保证数据会出现一致。典型AP系统例子包括某些NoSQL数据。AP系统在出现分区时仍然能响应用户请求,可是无论是否出现分区,都保证数据一致性,在出现冲突时候,可能使用某种启发性冲突消除算法,比如,用最后一次写入值作为有效值,或者对多笔写操作进行累计和叠加。亚马逊Dynamo数据库就是一个这样AP系统。最后一种是CA系统,这种系统则在分区时能够保证数据一致性,并且所有节点都能提供服务,可是出现分区时无论是C还是A都未必能够保证,节点未必有响应,就算有响应,数据也可能出现一致。一般在设计分布式系统时,都会放弃P,然后根据系统实际需求,会在C和A之间作取舍。

    CAP理论应用可以很灵活,在实际系统中,取舍粒度可能非常小。子系统可以采用CP或者AP,甚至同一个子系统内部同事务,也可以分别采用CP或AP。另外C和A也是绝对,近年流行起来最终一致性系统就是这样例子。根据应用语义,可能要求非常严格C,也可以相当宽松。比如用户在服务器上发起向在购物车中添加商品请求,只要最后能把所有请求合并,就能得到正确结果。对A要求也是很灵活,比如二阶段提交要求所有参与事务节点都有响应,而Paxos算法只要求多数节点可用。有应用要求节点响应延迟在毫秒级,有只要求在若干秒内响应即可。

    所有一致性算法,包括Paxos,都属于CP这一大类。在出现分区时,如果某个分区能够满足节点数过半条件,Paxos算法就能够继续执行,否则就会阻塞。


    Paxos算法
    有了之前讨论,我们已经知道,一个分布式状态机,每个节点都可以接受请求,然后用Paxos算法决定请求执行顺序,并且这样系统是一个CP系统,优先保证一致性。用Paxos算法保证请求执行顺序,具体来说,指

    (1)是每次决定采用请求,必定是从参加竞选提案里挑出来——非平凡性。
    (2)一旦第i个请求采用哪个提案已经决定好了,就会再采用内容提案了——一致性。
    (3)只要能联系上节点超过总节点数一半,并且这些节点都正常工作,那么一次竞选肯定能产生一个结果——非阻塞性。

    只要能保证这三个性质,Paxos算法就可以说是一个正确算法了。下面一边介绍算法,一边要简单说明这三个性质是如何成立

    2.1 Paxos算法角色
    Paxos算法里,每个节点关系都是对等,都可以发起提案,也可以接收提案,在LamportThe Part-Time Parliament这篇论文里,就是采用这种模型。但是如果能够将提案发起方、接收方和事务最终执行者这些角色分开来讨论,会更容易明白。Lamport在Paxos Made Simple这篇文章里采用了这种角色分离模型。把所有角色合并到同一个节点上之后,就得到了原来Paxos算法模型,所以两者实际是一样,我们采用分角色模型来讨论。

    (1)Proposer:接收客户请求,代表客户向Acceptor发起提案。
    (2)Acceptor:监听来自Proposer提案,并决定是否接受和回复。
    (3)Learner:提案被接受后,提交并执行提案内容。

    2.2 Paxos算法描述
    Paxos要求Proposer每个提案,都有一个唯一编号。所有编号必须能形成线序。做到这一点很简单,比如为每个节点编号host_id:1,2,3……,然后每个节点发出提案号n = x * 100 + host_id,其中x为自然数。这样就能得到101、102、201、202这样提案号了,这样提案号能够满足线序要求。提案号必是整数,他们实际值没有用处,只要求互相能比大小。另外每个Proposer发出提案编号应该是递增,新发出提案编号要比旧大。多数票通过提案,可称法案。为了决定某一个法案值,需要两个阶段。

    阶段1:竞争提案编号。
    (a)想要发起提案Proposer自行选择一个提案编号n,向过半数Acceptor发送Prepare消息,消息内容只包含提案编号n,并不包含想要提议值。
    (b)Acceptor记录自己接收过Prepare消息提案编号最大值。如果接收到Prepare消息提案编号n,小于等于有记载最大值,就忽略这个消息。如果n大于有记载最大值,那么Acceptor就要向Proposer回复一个Promise消息。如果Acceptor曾对某个提案运行到了阶段2,那么Promise消息内容是在阶段2中曾被接受提案值及其对应提案编号。如果Acceptor未曾对任何一个提案运行到阶段2,那么Promise消息内容就是“未决”。Promise消息意思是,告知Proposer其选择提案编号是目前见过最大,并且保证将来会接受小于等于这个提案编号消息了。

    阶段2:提交提案。
    (a)如果Proposer在一段时间内,没能够得到多数Acceptor回应Promise消息,就表明竞争提案编号失败了,递增自己提案编号,开始新一轮阶段1。如果得到多数AcceptorPromise,下一步就是要实际提交提案。首先要决定提交提案值。找出收到Promise消息中带最大提案提案编号,把其提案值作为要提交提案值,如果所有Promise消息内容都是“未决”,那么Proposer就可以按自己需要决定提案值,这个值当然就是客户本次提交请求了。接着Proposer向多数Acceptor发送Accept消息,消息内容是之前竞争成功提案编号n,以及刚才决定好提案值。
    (b)Acceptor收到Accept消息之后,检查其中提案编号n,如果之前针对更大提案编号发送过Promise消息,那么就忽略这个Accept消息,否则这个Accept消息就是目前见过最大编号提案,应当接受为法案。一旦一个提案接受为法案,Acceptor就向Proposer和所有Learner发送Accepted消息,消息内容自然是法案编号和其法案值了。Learner在收到Accepted消息后,就可以提交法案了。

    在State Machine Approach中,第i个法案值对应了第i个请求值。每个节点是一个Proposer兼Acceptor,都尝试把自己收到客户请求提案为第i个法案,如果没通过就尝试提为第i+1个法案。Paxos保证,只要第i个法案值被多数票通过,后续对同一个法案再提起提案永远都等于原来已通过法案值。因此即使一个提案成功结束,但是法案也可能未采纳当前节点提案值,而是早有其他节点捷足先登,这时也要尝试继续竞争第i+1个法案值。Paxos算法巧妙之处在于,在没进入第阶段2之前,Proposer提案值都还未确定,进入阶段2时,并不要求Acceptor去接受新法案值,而是要求Proposer屈从已有法案值。

    2.3 Paxos算法简单论证
    由于所有法案值都是从提案值中选出,因此非平凡性不言自明。

    要说明一致性,首先需要换个比较好具体等价陈述。一致性:假设任意两个编号同提案都能通过Paxos算法阶段2,那么这两个提案内容肯定是一样
    对两个编号提案B1和B2,他们多数派Q1和Q2具体成员可能是,但是在总成员数情况下,两个多数派里至少有一个成员是相同失一般性,可以假设B1小于B2,并且B1和B2是两个序号紧挨着提案。并且定义P1是B1发起者,P2是B2发起者,多数派交集里那个相同成员是Q。综合起来首先可推出一点:Q向P1发出Accepted消息时刻,要早于Q收到P2Prepare时刻。反之,如果Q收到P2Prepare比较早,因为B1小于B2,Q就会给P1发Accepted了,这与B1成功通过阶段2相矛盾。因为一个Accepter总是先收到Prepare,才发出Promise,所以按照时刻早到晚排列,时间发生顺序是,Q向P1发出Accepted < Q收到P2Prepare < Q向P2发出Promise。也就是说,Q向P2发出Promise内容,肯定包含了之前P1已经通过法案内容。按照Paxos算法阶段2步骤(a),P2遍历所有收到Promise消息时,会发现Q发出Promise消息带有提案编号最大,因此把其提案内容作为将来发送Accept消息提案值。

    对于B1大于B2情况,可以把上面证明P1和P2角色对调一下,还是成立。对于B1和B2是紧挨着情况,那么肯定有一个提案B在B1和B2中间,用同样办法证明B1和B提案内容一样,因此B和B2提案内容也一样。

    非阻塞性实际上分为2种情况,(1)只有一个Proposer,(2)有多个Proposer在竞争同一提案。情况(1)是显然,但是对于情况(2),Leslie Lamport所写The Part-Time Parliament、Paxos Made Simple、Disk Paxos和Fast Paxos这几篇文章里,都没有证明非阻塞性。The Part-Time Parliament只证明了,当在线节点足够多时,通过法案可行性,实际上是证明了情况(1)。Disk Paxos附录中说,非阻塞性是很明显,所以需要正式证明,其实也是指情况(1)是明显。其实情况(2)并不明显。比如,有两个Proposer竞争同一个法案,P1发起Prepare提案编号1,Acceptor接受并回应Promise。接着在P1发送Accept之前,P2发起Prepare提案编号2,因此Accptor也接受并回应Promise。等到P1发送Accept提案编号1时候,Acceptor发现P2编号2比较大,因此没有接受Accept消息。接着P1将提案编号自增到3,发送Prepare消息,因此Acceptor也接受并Promise。可是等到P2发送Accept编号2时候,Acceptor发现P1提案编号3比较大,接受,因此P2也自增提案编号。这样周而复始,陷入活锁。

    解决方法有两个。在Proposer发现提案没被接受时,休眠一段时间,时长随机,并且每次失败后,休眠时长倍增。这样就能互相避开。或者按照Lamport建议,通过某个其他算法,确定一个领导节点,让领导节点优先,其他节点暂时退避,或者只允许领导节点发起提案。但是Paxos算法本身就时一个一致性算法,用来实现领导节点选举算法,所以在这种场合下就形成了对领导算法循环依赖,是没办法实现。再说,如果已经存在一个其他选举算法,那么也就几乎用不着Paxos算法了。因此各节点随机退避做法比较可行。

    从这些简单论证中可以看出Paxos关键点有两个。第一个在它假设里,Paxos要求任意两个提案投票节点集合必须有交集,这一点可以通过要求投票节点数目过半达到。第二个关键点在算法本身里,Proposer发起真正提案内容,在阶段(1)结束时才决定,而是一开始就决定。这两个关键点连在一起,决定了一个较早提案只要通过了阶段2,就肯定会被其他多数派感知,而Proposer要服从多数派意见,较晚提案值总是和较早提案值一致。Paxos算法一致性,是要求所有成员都通过同一个值,而只要求多数派通过,那么,想要查询这个值时候怎么办呢?如果只向单个节点查询,这个节点可能在多数派里,查到值可能是错误。可以通过一次新提案来实现对法案X内容查询,提案内容是“未决”。这样如果原来法案X存在,那么提案成功后,它仍然没有内容,如果法案X已经有值了,那么Paxos算法保证了本次提案通过值是一致,Proposer在阶段1结束时候就已经知道法案值是多少了。

    形式化论证请参考The Part-Time Parliament。

    2.4 Paxos里两个阶段必要性
    Paxos算法要求先竞争提案编号,再竞争提案值,看上去比较复杂。是否可以简化一下,直接竞争提案值呢?比如,所有Acceptor都实行“先到先得”策略,只通过首次接收到提案,拒绝其他所有提案。这个策略问题在于,某些Proposer依次或者同时发起提案,由于网络故障,提案请求可能在中途部分丢失,每种提案都可能都只有同少数派Acceptor接收到,每个提案都无法形成多数派,形成死锁。如果反过来,所有Acceptor都实行“后来居上”策略,总是接受请求,以最后接收为准,也会出现问题。当一个Proposer通过发送提案确保了大多数通过之后,其他Proposer又可以在多数派里推翻提案,失去了一致性。Paxos算法没有这种问题,阶段1是竞争提案号,策略是“后来居上”,在这期间如果没有其他Proposer出现,就进入阶段2“先到先得”。而即使在“先到先得”阶段发生了丢包,也会形成死锁,因为在阶段1结束时候新“后来居上”多数派成员要么(1)与之只前少数派没有交集,因此自由提交形成死锁;要么(2)有交集,因此少数派意见会通过Promise消息返回,而Proposer会按照算法要求,选用少数派已通过法案进行阶段2提交,于是少数派成为多数派,整个算法得以运行下去。

    从以上讨论可以看出,后来居上阶段1和先到先得阶段2,都是必须。同时阶段2还需要保证一点,就是提案存储必须是持久,否则一旦进程崩溃,信息就丢失了,因此Acceptor信息,包括法案号、法案内容、见过最大提案号,都需要在收到消息或者处理结束时即时写入磁盘。


    3 Disk Paxos算法
    Disk Paxos适用于SAN环境。通过把Paxos算法中需要记录信息从本地磁盘转移到SAN上,把收发消息改变为读写LUN上数据,可以得到Disk Paxos算法。在SAN环境下,使用专门光网访问存储,时延小,吞吐量大。SAN环境中LUN后端可以配置成磁盘阵列,有校验和冗余性保证,稳定性很好,相比之下,计算节点(进程或者OS)稳定性没有那么高。所以从实际应用角度来说,Disk Paxos比基于计算节点和消息传递Paxos更可靠。

    Disk PaxosPaxos一样,都分为两个阶段。参与Disk Paxos所有进程都有一个属于自己磁盘区域,发起提案时候,首先写自己磁盘区域,然后读其他进程磁盘区域。如果发现读到其他进程也发起提案并且编号比自己大,就放弃。第一阶段只竞争提案号,涉及提案值。如果没有读到比自己大提案号,就可以准备进入阶段2。在阶段1读操作结束时,即使自己提案号可能比其他进程提案号大,但是如果发现已经有其他进程通过阶段2提交过提案内容,那么本进程选择提案内容必须是一样,如果没有发现其他进程提交过,就可以自由选择提交内容。阶段2也是一个写操作和一个读操作,写操作把提案编号和内容写入自己磁盘区域,再读其他进程磁盘区域,如果没有发现比自己大提案编号,就结束阶段2,认为系统达到了一致。容错是通过同时读写多块磁盘上同一区域来实现,假设有N块盘,对于一个写操作,如果能够写入大于等于N/2+1块盘,就是确保了大多数盘都写入成功。至于其他进程是否在运行,还是崩溃,都没有关系,所有信息都以磁盘上为准。如果有节点崩溃了,恢复后读一下LUN,如果多数盘结果能一致,就回到同步好状态了。Disk Paxos实际上是把SAN当成了一块共享黑板,把原来需要保存在本地磁盘和内存中数据转移到了SAN上保存。

    3.1 Disk Paxos算法正式描述
    3.1.1 符号

    dblock:某个LUN上专门划分出来一块区域,用来存放Disk Paxos算法运行时信息。每个参与Disk Paxos算法计算节点都能够从dblock上获得一块区域,存放自己信息。节点也可以从dblock上读到为其他节点分配磁盘区域。
    p:参与算法某个节点或者进程。
    dblock[p]:参与算法节点p在dblock里维护磁盘区域,这个区域里又包含了下面信息。
    dblock[p].mbal:本次提案提案号
    dblock[p].bal:p进入阶段2时最大提案号
    dblock[p].inp:p以dblock[p].bal进入阶段2时,提交提案内容

    dblock[p]有2个副本,一个在进程p内存里,一个写入到了磁盘上。写入到磁盘上dblock才算是真正生效了,才能被其他进程读取。在下面算法描述中,凡是没显式说明读写磁盘,dblock[p]都是指在内存中那份副本。

    3.1.2 算法
    (1) 进程p初始化算法
    接收到用户输入input[p]
    dblock[p].mbal自增
    创建集合blocksSeen,初值为{dblock[p]}
    进入阶段1

    (2) 进程p阶段1和2算法
    对SAN里分配给算法用每块磁盘d,同时执行:
        将dblock[p]写入disk[d][p]
        对每个参与进程q,且q等于p者,执行
            读取disk[d][q],将读取结果插入blocksSeen
            如果发现disk[d][q].mbal > dblock[p].mbal,则取消本次提案
        如果已经对多数磁盘都执行成功了,可以直接成功终止循环
    如果p处于阶段1
        令dblock[p].bal等于dblock.mbal
        定义集合nonInitBlks为 {bk | bk属于blockSeen,且bk.inp等于“未决”}
        如果nonInitBlks为空
            那么令dblock[p].inp等于input[p]
            否则,令dblock[p].inp等于bkMax.inp,其中bkMax是nonInitBlks里bk.bal最大者
        进入阶段2
    否则提交dblock[p].inp为法案

    (3) 节点崩溃重启后恢复算法
    创建集合tempSet,初值为空集
    对SAN里分配给算法用每块磁盘d,同时执行:
        读取disk[d][p],并插入tempSet
        如果已经对多数磁盘都执行成功了,可以直接成功终止循环
    令dblock[p]等于bkMax,其中bkMax是tempSet里bk.mbal最大者
    进入进程p初始化算法

    以上就是Disk Paxos正式描述了。如果觉得有点绕,可以结合之前非正式描述品一品。对于Disk Paxos来说阶段1和阶段2都是一个写操作、一个读操作。Disk Paxos正确关键就是写和读顺序,读必须在写之后,节点才能确认自己提案号是否是最大

    3.2 Disk Paxos简单论证
    Disk Paxos正确性证明也有三点,非平凡、一致、非阻塞。论证方式基本上和原始Paxos算法一样。详细过程可以参考Disk Paxos论文。下面谈一谈我自己分析,从和Disk Paxos论文角度,讨论Disk Paxos是如何让同时发起两个提案达成一致

    假设有两个节点A和B同时准备提交提案,第一阶段3种情况
    情况1:A写 A读 B写 B读
    情况2:A写 B写 A读 B读
    情况3:A写 B写 B读 A读
    在一个阶段里那么可能发生顺序就有上面三种,还有三种情况是对称,可以忽略。情况2和情况3里,A和B都能读到对方提案号,肯定有一个进程因为提案号小而放弃并稍后重试,会都认为自己成功了。只有情况1会可能会出现A都觉得自己成功了,都进入阶段2情况。因为在情况1里,A读在B写之前,所以A知道B可能有一个更大编号,这样就会出现有多个进程进入阶段2情况。下面只需要针对2个进程都在情况1里进入阶段2来讨论就清楚了。

    从情况1进入阶段2时候,又有三种情况,其中A写'代表A在阶段2写操作
    情况1.1:A写 A读 A写' B写 B读
    情况1.2:A写 A读 B写 A写' B读
    情况1.3:A写 A读 B写 B读 A写'
    情况1.1这个大情况里,如果A提案号比B大,B读以后能发现这一点,B放弃并稍后重试,只有A继续。根据算法定义,B将来提交提案值将采用A写'写进去值,系统保证了一致。

    下面只讨论A提案号比B情况,那么情况1.1又可以细分出2种情况。
    情况1.1.1:A写 A读 A写' A读' B写 B读
    情况1.1.2:A写 A读 A写' B写 A读' B读
    即使A提案号比B小,1.1.1里A是可以完成阶段二并且提交,这时因为B读在A写'之后,B会选择与A写'写入值一样提案内容继续下去,系统保证了一致。
    情况1.1.2里A在阶段二读时会发现B提议了一个更大编号,A放弃,B继续,系统保证了一致。A重试时将落入情况1.1.1,只不过AB身份对调。

    下面讨论1.2和1.3,如果A提案号比B大,那么1.2和1.3B读就能检测到这个情况,B放弃,A继续。B重试时将落入情况1.1.1。
    下面讨论A提案号比B小情况。1.2和1.3共同点是A写'发生在B写之后,因此A读'肯定也在B写之后,如果A提案号小,那么A读'就能检测到这个情况,因此A放弃,B继续。A重试时落入情况2或者3,只不过AB身份对调。

    3.3 Disk PaxosPaxos对应关系
    在阶段1,Disk Paxos写操作与PaxosPrepare消息是对应,Disk Paxos读操作与PaxosPromise消息是对应。向磁盘写dblock[p],其实相当于向所有节点发送了Prepare消息,而读其他节点dblock[q],其实就是接收所有节点Promise消息。唯一区别在于,提案节点p必须自行判断读到dblock[q].mbal里是否存在比dblock[p].mbal大者,而在Paxos里,如果Acceptor发现见过提案号比收到Prepare消息大,就直接回应了。对于判断是否自己mbal最大这个逻辑,Disk Paxos是在发起提案节点上自行判断Paxos是由Acceptor判断,实际上只要是执行对了这个判断,判断在哪一端执行是无所谓。对阶段2,情况也是类似。这样一来,可以发现实际上Paxos算法里Acceptor只是充当了一个过滤器角色,Acceptor永远都只同意提案,但是它总是滤掉那些提案号比较小提案,主要约束都是针对Proposer。这样一思考就发现Disk Paxos算法里没有Acceptor也没有问题,只要把过滤器相关逻辑移动到Proposer身上就可以了。

    3.4 Sanlock
    接触到Paxos算法是因为我参与oVirt项目开发用到了Sanlock。oVirt是一个开源虚拟化管理平台,简而言之,就是用户配置好一群主机后,纳入到oVirt管理中,oVirt有一个统一Web界面让用户自助在集群主机上启动虚拟机,创建存储池,创建虚拟网络。在oVirt中,每台物理主机都可以直接访问集群存储,为了保证集群存储元数据一致性,需要一个领导节点来统一执行所有影响存储元数据操作(比如分配新卷)。在领导节点崩溃或者失去联系时候,需要选举出新领导节点,这样才能保证集群存储可用性。选举新领导节点时候又需要保证,所有节点对哪个节点是领导节点达成一致,否则,出现两个领导节点,同时读些集群存储元数据,迟早会造成元数据损坏。oVirt项目使用Sanlock保证领导节点唯一性,Sanlock,顾名思义,就是以存储区域网络为中心分布式锁。Sanlock项目需要用户为每台主机分配一个id,并且在存储区域网络中分配一些空间用于存放分布式锁记录信息,每台主机都可以尝试对某个假想资源上锁,Sanlock保证只有一台主机能成功拿到对这个资源锁。至于这个假想资源具体代表什么,就由调用Sanlock应用自行决定。它语义和常见Mutex很相似,都是对某个资源上锁,用这个锁去保护什么则由程序员自己决定。锁也是君子锁,上锁而直接访问资源也是可以,后果自负。Sanlock是把常见mutex语义扩展到分布式领域。Sanlock分布式锁就是基于Disk Paxos算法实现

    3.4.1 租约获得、超时和更新
    Sanlock中锁实际上是一种类似租约(lease)机制。在分布式环境下,某个进程对资源上锁后可能会崩溃,永远会恢复,那么资源就被占住了,所以分布式是一劳永逸,获得租约后需要刷新。如果刷新上超时了,租约就自动被释放了。当某个节点要获取一个租约,首先利用Disk Paxos算法,发起一个提案,提案内容是这个节点自己ID,以及当前时间戳。提案通过后,其他节点通过读取法案内容,就知道谁是租约所有者,以及租约生效时间。如果经过比如60秒,租约还没有更新,其他节点就认为原来所有者已经崩溃,或者网络故障导致它无法续租,就可以竞争成为新所有者。

    要更新一个租约,其实是更新法案时间戳。按照State Machine Approach思路,要决定租约当前状态,只需要重放到目前为止所有状态更新请求,所以要更新租约,只需要针对一个编号更大状态发起一次提案。状态编号和提案编号mbal是两回事。状态编号指是State Machine Approach中一串输入各自编号,由于输入决定了状态,所以对状态和输入可以用同一个编号。每个输入都是一个法案,每个独立法案有自己bal,用来记录对这个法案本身发起最高提案编号,每个法案决定都是一次独立Disk Paxos运行。这里可以进行一些伤害算法正确性化简。首先,租约与一般意义上状态(比如账号余额)同,租约历史刷新请求对当前租约实际归属没有影响,只有最近一次成功刷新才决定了租约归属,所以所有旧法案都会有节点去关心,我们只保存针对最新请求法案就可以了。其次,算法没有规定法案和提案mbal、bal初始值,只要求编号之间可以比大小。因此,对新输入提起法案完全可以以上一个法案mbal、bal作为初始值。第三,输入值编号要求是单掉增长,mbal和bal也都是单掉增长,所以可以直接以bal作为最新输入值编号,以mbal为下一个输入值编号。合并这三点,就得到一个结论:简化Disk Paxos算法和状态机模型后,可以仅使用一个法案来表示一个租约。只需要修改一下阶段1末尾决定inp算法就能达到这个效果。阶段1要求只有读到所有dblock[q].inp都是“未决”时,才能够自由决定inp内容。我们可以修改为,检查inp里时间戳,如果inp时间戳没有超时,但是租约所有者和提案发起者是同一个节点时,认为inp等价于“未决”。如果时间戳离当前时间已经超过60秒,也可以认为其等价于“未决”。这样租约所有者就可以在没过期之前自由更新时间戳,租约竞争者在租约到期之后也能够有机会成为新所有者。

    3.4.2 Sanlock其他特性
    Sanlock在运行时,首先需要分配一个锁空间,这个锁空间就是Disk Paxosdblock存储。此外,锁空间还包含了所有节点ID信息,节点ID在Sanlock作用很大,用来标记租约所有者,而节点ID本身是可以动态分配,因此在分布式环境下也需要用租约来保护。节点ID租约没法再用Disk Paxos算法,而是用了另外一个基于共享存储租约算法“Light-Weight Leases for Storage-Centric Coordination”,节点ID租约在Sanlock里被称为Delta Lease,而用Disk Paxos获得租约称为Paxos Lease。Delta Lease用到算法本质上也是写后读,首先把自己信息写到共享磁盘上,然后等待相当长一段时间再读,看读到内容是否有变化。如果没有变化,就表示没有冲突,租约成功获取。这个算法导致Delta Lease获取时间特别长,因此只用来保护节点ID,一般租约都用Disk Paxos算法,速度很快。

    除此之外,Sanlock还自带了一个Watch Dog,可以操作/dev/watchdog设备。在一个节点更新租约失败之后,常常需要把它从集群里fence出去,通过操作/dev/watchdog,节点发现自己更新了租约时,可以自己把自己fence出去。所谓fence,就是发现一个进程/主机工作状态秀逗之后,就彻底断开它网络,或者把这个节点关闭、重启。原因是在高可用集群中,一个服务器秀逗时往往会有替代服务器自动启动,而我们一般是无法很好区分高时延、节点崩溃、网络拥塞、网络故障这几种故障模式,如果只是短暂拥塞,替代服务器启动了,原来服务器又没关闭,两个服务器都以为自己独占了某一个LUN,就会同时发起读写,破坏掉应用数据。因此凡是发现服务器秀逗时候,就要把它从集群中隔离出去。Sanlock后台程序会打开系统看门狗设备,并且喂狗,一旦某个客户进程租约更新失败,会尝试杀死进程,如果杀死就再喂狗,看门狗就会自动重启整个服务器,以此达到自我fence目的。


    4 Google Chubby
    GoogleChubby项目主要设计目标是为分布式系统提供一个可靠粗粒度锁服务。它和Sanlock目标很像,同之处在于Sanlock需要SAN作为基础设施,而Chubby则使用经典Paxos算法,只通过消息传递来实现锁。在分布式系统中,实现全分布式细粒度开销很大,因此无论是Chubby还是Sanlock,都主要用于实现粗粒度锁,常见用途比如选举领导节点。粗粒度锁一旦被获取,在较长时间内都会释放,这样上锁开销就可以忽略。细粒度锁需要频繁获取和释放,因此锁开销太大会使得它失去意义。可以先选举出领导节点,然后可以再由领导节点自己实现细粒度锁。因此粗粒度锁服务常常是分布式系统中基础服务之一。Chubby开发出来之后,提供给Google其他服务使用,客户包括GFS和Bigtable。Chubby核心是一个用Paxos算法和State Machine Approach实现日志分发和同步层,之上是高容错分布式数据库层,支持快照和日志截断,再往上就是ChubbyRPC协议层了。

    据说LamportThe Part-Time Parliament论文因为过于文艺,被各种拒绝发表,编辑都要求将文艺部分移除。Lamport认为编辑们太没幽默感,拒修改。后来Chubby团队需要一个一致性算法,Lamport把自己论文给他们看了,这些工程师马上心领神会,设计实现了Chubby。于是Lamport找了个有幽默感编辑再次投稿,终于发表。Chubby在实现Paxos算法过程中解决了很多在生产环境中才能遇到问题,并且对Paxos进行了优化和补充。比如对于集群成员管理,本身就是一个需要一致过程,新节点加入或者旧节点退出,会导致总节点数变化,因此法案多数派成员数也会发生变化。Lamport认为Paxos算法本身就可以用来实现成员管理一致性要求。实际上操作起来是那么简单,因此需要进一步研究和讨论。在生产环境中,Chubby必须面对这样问题,Chubby是很好分布式锁服务研究范例。


    5 Amazon Dynamo
    AmazonDynamo是一个分布式key-value数据库。主要设计目标是高可用。Dynamo使用常见Consistent Hashing来对数据和节点进行分区,并且为每份数据在多个节点上保存副本,以达到高可靠、高可用、高性能要求。Dynamo为了达到高可用,必然要对一致性做出一些牺牲。Dynamo没出错一切都好,出现错误时数据可能会出现冲突。为了解决冲突,Dynamo对数据标记版本,对同版本数据需要客户端程序自行解决冲突并合并。另外,为了保证同一份数据在多个副本节点上一致性,Dynamo提出了一种RWN机制。R、W、N都是可配置参数,N代表节点总数,R代表一次读操作要求有多少个节点参与并且成功,W代表写操作在多少个节点上成功。配置系统参数为R+W>N时,系统行为更偏向追求一致性,但是在出现网络故障时就没有可用性了,配置R+W<=N时,更偏向可用性,网络故障时就失去一致性。RWN提供了一种比较灵活调整系统CAP权重机制。对比RWN和和Paxos可以发现,当R>=N/2+1且W>=N/2+1时,RWN系统行为有点像只有一个阶段Paxos。RWN并不是一种一致性算法,它也没有解决同时有多个写者时怎么达成一致。RWN和Paxos有各自适用场合。


    6 Paxosd
    自己实现一遍算法,就算真的学会了这个算法。Paxosd就是我看完Paxos相关几篇论文之后,自己用Erlang实现一个分布式锁服务(https://github.com/edwardbadboy/paxosd)。已有Erlang实现Paxos算法还有gen_paxos项目(gen_是Erlang中Behavior名字常见前缀,可以把Behavior类比成超类),有兴趣读者可以研究一下这个更成熟项目。实现Paxosd时,采用了Proposer、Acceptor和Learner分角色算法,每个角色有自己进程,通过Erlang消息机制通信。Paxos算法中操作都是通过收发消息进行,所以用Erlang实现起来特别简单,主要代码只有1200多行。首先在Paxosd里实现了基于Paxos提案机制,然后在此基础之上,用实现了用Paxos协议更新租约。对外公布API包括:
    joinCluster/0:让当前节点加入集群。
    waitNodes/1:等待集群可联系成员达到大多数要求。
    propose/2:对某个话题发起一个提案。
    learn/1:查询某个法案值。
    leaseGet/1:获得一个租约。
    leaseRefresh/1:续约。
    leaseRelease/1:释放租约。
    leaseWhose/1:查询租约所有者。
    leaseWait/1:等待一个被其他节点占有租约可用后,再次竞争租约,直到成功为止。
    (在Erlang中,函数名/X表示函数有X个参数)

    新加入节点都是通过两个Joker节点加入集群。要启动一个Paxosd集群,首先启动两个Joker节点,并让他们互相连接上,接着后面启动节点调用joinCluster/0,就会向Joker节点连接,由Joker将其介绍进集群。另外还基于Common Test写了分布式功能测试。该测试自动启动Joker节点,并且启动若干Paxosd节点,然后让他们加入集群,接着在每个Paxosd节点都尝试获取一个租约,获取成功者对一个在集群间共享变量进行自增,重复执行这个动作若干次,测试结束时候,看共享变量终值是否正确。


    7 小结
    Paxos最经典论文当属LamportThe Part-Time Parliament。这篇论文过于文艺,是直接介绍Paxos算法,而是作了一个比喻,虚构出一个古希腊小岛,用小岛上兼职议会投票问题影射分布式领域一致性问题,读者得有一定幽默感和文艺细胞才能更好欣赏这篇论文。并且作者为了让论文看起来更像考古学文本,用了很多稀奇符号,举例时用人名全由大写希腊字母组成,如果没有耐心简直读下去。过真的读下来,还是很有趣。如果觉得The Part-Time Parliament太难啃,可以先读Paxos Made Simple,非常简练介绍了Paxos算法,堪称秒懂。然后再结合维基百科上总结几个变种和优化,看看Chubby论文和一些Paxos协议参考实现,能清楚很多。

    还有一些话题没有详细介绍,比如,Paxos消息序列图解、用Paxos对集群成员进行管理、Paxos其他变种和各种优化手段、其他几个用Erlang实现Paxos算法项目。过相关资料在网络上都很容易查到,大多数话题看一遍Paxos维基百科(中、英文版内容同,互相补充)基本都能找到一些引文链接或者关键词,进行深入研究。

    参考文献主要是Lamport自己写几篇Paxos论文,在前文都提到了名字。Sanlock在网上能搜到幻灯,也可以读一下它代码。Google Chubby和AmazonDynamo也都公布了论文,都能搜到。具体哪些地方引了哪些文章在第几页就标了,估计你们也会去看,我打字也有点累了。。。

  • 相关阅读:
    根据坐标点画图形
    js 解析geojson格式
    devexpress 安装
    DataTable 获取列名
    ADO.NET 注册
    css:outline
    javascript函数sort
    引用类型-2015/10/06
    2015-11-02-js
    jquery
  • 原文地址:https://www.cnblogs.com/shaohef/p/4499881.html
Copyright © 2011-2022 走看看