Paxos算法浅析

前言
在文章2PC/3PC到底是啥中介绍了2PC这种一致性协议,从文中了解到2PC更多的被用在了状态一致性上(分布式事务),在数据一致性中很少被使用;而Paxos正是在数据一致性中被广泛使用,在过去十年里,Paxos基本成为了分布式领域内一致性协议的代名词。Google的粗粒度锁服务Chubby的设计开发者Burrows曾经说过:“所有一致性协议本质上要么是Paxos要么是其变体”。Paxos的提出者LeslieLamport也因其对分布式系统的杰出理论贡献获得了2013年图灵奖。
在介绍Paxos之前,先介绍一下数据一致性到底被用在什么场景中,下面以副本状态机来表述

副本状态机
在分布式环境下,一致性协议的应用场景一般会采用副本状态机来表达,这是对各种不同应用场景的一种抽象化表述。
一种典型的实现副本状态机的机制是采用Log副本的方式,如下图(来源网上):
qq%e6%88%aa%e5%9b%be20161217123221
集群中多台服务器各自保存一份Log副本及内部状态机,Log内顺序记载客户端发来的操作指令,服务器依次执行Log内的指令并将其体现到内部状态机上,如果保证每台机器内的Log副本内容完全一致,那么对应的状态机也可以保证整体状态一致。
一致性协议的作用就是保证各个Log副本数据的一致性,上图中的一致性模块就是用来保证一致性的。
再来看一个更具体的例子:在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态。为保证每个节点执行相同的命令序列,需要在每一条指令上执行一个「一致性算法」以保证每个节点看到的指令一致。

民主选举算法
如何保证各个Log副本数据的一致性(或者说如何来实现这个一致性模块),可能最先想到的是只需要提供一个唯一一致性模块,然后用类似2PC的方式来保证数据的一致性,但是我们也知道了2PC方式中面临着一致性模块的当机以及网络的异常等问题,最终导致数据出现不一致;而本文要介绍的Paxos一致性协议就是如何在可能发生几起宕机或网络异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性,主要原因还是Paxos提供了集群一致性模块,然后以民主选举的算法——大多数的决定会成个整个集群的统一决定。任何一个点都可以提出要修改某个数据的提案,是否通过这个提案取决于这个集群中是否有超过半数的结点同意(所以Paxos算法需要集群中的结点是单数);当然这个保证是有一个前提的,这就是下面要介绍的拜占庭问题。

拜占庭问题
其故事背景是这样的:拜占庭位于现在土耳其的伊斯坦布尔,是东罗马帝国的首都。由于当时拜占庭罗马帝国国土辽阔,为了防御目的,因此每个军队都分隔很远,将军与将军之间只能靠信差传消息。 在战争的时候,拜占庭军队内所有将军必需达成一致的共识,决定是否有赢的机会才去攻打敌人的阵营。但是,军队可能有叛徒和敌军间谍,这些叛徒将军们会扰乱或左右决策的过程。这时候,在已知有成员谋反的情况下,其余忠诚的将军在不受叛徒的影响下如何达成一致的协议,这就是拜占庭将军问题。
从理论上来说,在分布式计算领域,试图在异步系统和不可靠的通道上来达到一致性是不可能的,因此在对一致性的研究过程中,都往往假设信道是可靠的,即假设不存在拜占庭问题。
非拜占庭模型定义:
1.一致性模块的行为可以以任意速度执行,允许运行失败,在失败后也许会重启并再次运行;
2.一致性模块之间通过异步方式发送信息通信,通信时间可以任意长,信息可能会在传输过程中丢失,也允许重复发送相同的信息,多重信息的顺序可以任意。但是有一点:信息不允许被篡改。

paxos的基本概念
首先是并行进程(对应副本状态机上每台服务器的一致性模块)的角色概念,Paxos协议下不同并行进程可能承担的三种角色如下:
倡议者(Proposer):倡议者可以提出提议(数值或操作命令等)以供投票表决;
接受者(Acceptor):接受者可以对倡议者提出的提议进行投票表决,从众多提议中选出唯一确定的一个;
学习者(Learner):学习者无倡议投票权,但是可以从接受者那里获知是哪个提议最终被选中;
在一致性协议框架中,一个并行进程可以同时承担以上多种角色。
划分角色后,就可以更精确的定义问题:
1.决议(value)只有在被 proposers 提出后才能批准(未经批准的决议称为「提案(proposal)」);
2.在一次Paxos算法的执行实例中,只批准一个Value;
3.Learner只能获得被批准(chosen)的Value。

Paxos一致性协议
Paxos的目的是在非拜占庭条件下,当多个并行进程提出不同的倡议时,如何能够达成一致。如果归纳Paxos协议,可以将其描述为以下两阶段过程:
阶段一:Prepare阶段
1.1【倡议者视角】倡议者选择倡议编号n,然后向大多数(即超过半数以上)接受者发送Prepare请求,请求中附带倡议编号n。
1.2【接受者视角】对于某个接受者来说,如果接收到带有倡议编号n的Prepare请求,则做如下判断:若倡议编号n比此接受者之前响应过的任何其它Prepare请求附带的倡议编号都大,那么此接受者会给倡议者以响应,并承诺不会响应之后接收到的其它任何倡议编号小于n的请求,另外,如果接受者曾经响应过2.2阶段的Accept请求,则将所有响应的Accept请求中倡议编号最高的倡议内容发送给倡议者,倡议内容包括两项信息:Accept请求中的倡议编号以及其倡议值。若倡议编号n不比此接受者之前响应过的任何其它Prepare请求附带的倡议编号都大,那么此接受者不会给倡议者以响应。

阶段二:Accept阶段
2.1【倡议者视角】如果倡议者接收到大多数接受者关于带有倡议编号n的Prepare请求的响应,那么倡议者向这些接受者发送Accept请求,Accept请求附带两个信息:倡议编号n以及倡议值v。倡议值v的选择方式如下:如果在1.2阶段接受者返回了自己曾经接受的具有最高倡议编号Accept请求倡议内容,则从这些倡议内容里面选择倡议编号最高的并将其倡议值作为倡议值v;如果1.2阶段没有收到任何接受者的Accept请求倡议内容,则可以任意赋值给倡议值v。

2.2【接受者视角】如果接受者接收到了任意倡议编号为n的Accept请求,则接受者接受此请求,除非在此期间接受者响应过具有比n更高编号的Prepare请求。通过以上两阶段过程即可选出唯一的倡议值,对于学习者来说,其需要从接受者那里获知到底是哪个倡议值被选出。一个直观的方法如下:每当接受者执行完2.2步骤,即接受某个Accept请求后,由其通知所有学习者其所接受的倡议,这样,学习者很快习得是哪个倡议被最终选出。但是这种方式会导致大量通信,因为任意一个接受者会通知任意一个学习者,如果有m个接受者,n个学习者,则需要m*n次通信。一个替代策略是:从众多学习者中选择一个作为代表,由其从接受者那里获知最终被选出的倡议,然后再由其通知其它学习者,这样可以将通信量降为m+n。但是这个方案中如果这个学习者代表发生故障,其它学习者无从知晓倡议值。考虑到健壮性和通信量两个因素,可以采取折中方法:选出若干学习者作为代表,由这些代表从接受者那里获知最终倡议值,然后通知其它学习者。

通过以上流程,如果有多个并发进程提出各自的倡议值,Paxos就可以保证从中选出且只选出一个唯一确定的倡议值,以此来达到副本状态机保持状态一致的目标。

总结
此文只是对Paxos的应用场景以及Paxos协议本身进行了介绍,而Paxos最难理解性在于是什么因素导致协议以此种方式呈现以及其正确性证明过程而非最终协议本身内容。