转自不正直的绅士,因百度空间迁移,无法注明出处,我从其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也都公布了论文,都能搜到。具体哪些地方引了哪些文章在第几页就标了,估计你们也会去看,我打字也有点累了。。。

最新文章

  1. AngularJS之ng-class(十一)
  2. Windows Server 2008 R2 服务器安装(重装)流程备忘
  3. Badboy录制脚本参数化
  4. JAVA中获取路径
  5. (一)keil4 MDK 开发环境下编写裸机程序 (参考杨铸 北航) (开发板只需要连接JLNK 就行了)
  6. T语言TC发布脚本方法
  7. html5 中meta中 content=width=device-width注意
  8. qt优点
  9. [POJ] 1064 Cable master (二分查找)
  10. oracle在schema是什么意思?
  11. 201521123071 《JAVA程序设计》第九周学习总结
  12. An Introduction to Variational Methods (5.1)
  13. BZOJ1895Pku3580 supermemo——非旋转treap
  14. 关于Unity中Mecanim动画的动画状态代码控制与代码生成动画控制器
  15. matlab中常用的函数
  16. js的组合函数
  17. Python全栈day21(调用模块路径BASEDIR的正确方法)
  18. ZOJ 3521 Fairy Wars oj错误题目,计算几何,尺取法,排序二叉树,并查集 难度:2
  19. Django项目使用七牛云存储图片
  20. 架构模式:MVC与MVVM

热门文章

  1. requirejs + vue 项目搭建2
  2. 从头开发MUDLIB
  3. 重读LPTHW-Lesson15-17
  4. delphi 读网页线程TReadHtmlThread
  5. 可爱的 Python : Python中的函数式编程,第三部分
  6. C语言入门(19)——C语言的编码风格
  7. 10-3[RF] feature selection
  8. aps.net要掌握的技术
  9. table+js实现网站左侧列表下拉隐藏
  10. jquery设置文本框值 与获取文本框的值