一、简介
Paxos算法是莱斯利·兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的、具有高容错性的一致性算法。Google Chubby的作者Mike Burrows说过,世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。Paxos算法是一种公认的晦涩难懂的算法,并且工程实现上也具有很大难度。较有名的Paxos工程实现有Google Chubby、ZAB、微信的PhxPaxos等。
二、Paxos与拜占庭将军问题
拜占庭将军问题是由Paxos算法作者莱斯利·兰伯特提出的点对点通信中的基本问题。该问题要说明的含义是,在不可靠信道上试图通过消息传递的方式达到一致性是不可能的。所以,Paxos算法的前提是不存在拜占庭将军问题,即信道是安全的、可靠的,集群节点间传递的消息是不会被篡改的。
一般情况下,分布式系统中各个节点间采用两种通讯模型:共享内存(Shared Memory)、消息传递(Messages Passing)。而Paxos是基于消息传递通讯模型的。
三、算法描述
(1)三种角色
Proposer :提议者
Acceptor:决策者
Client:产生议题者
Learner:最终决策学习者
(2)Paxos算法的一致性
Paxos算法的一致性主要体现在以下几点:
- 每个提议者在提出提案时都会首先获取到一个具有全局唯一性的、递增的提案编号N,即在整个集群中是唯一的编号N,然后将该编号赋予其要提出的提案。
- 每个表决者在accept某提案后,会将该提案的编号N记录在本地,这样每个表决者中保存的已经被accept的提案中会存在一个编号最大的提案,其编号假设为maxN。每个表决者仅会accept编号大于自己本地maxN的提案。
- 在众多提案中最终只能有一个提案被选定。
- 一旦一个提案被选定,则其它服务器会主动同步(Learn)该提案到本地。
- 没有提案被提出则不会有提案被选定。
(3)算法过程描述
Paxos算法的执行过程划分为两个阶段:准备阶段prepare与接受阶段accept。
准备阶段
1> 提议者(Proposer)准备提交一个编号为N的提议,于是其首先向所有表决者(Acceptor)发送prepare(N)请求,用于试探集群是否支持该编号的提议。
2> 每个表决者(Acceptor)中都保存着自己曾经accept过的提议中的最大编号maxN。当一个表决者接收到其它主机发送来的prepare(N)请求时,其会比较N与maxN的值。有以下几种情况:
a> 若N小于maxN,则说明该提议已过时,当前表决者采取不回应或回应Error的方式来拒绝该prepare请求;
b>若N大于maxN,则说明该提议是可以接受的,当前表决者会首先将该N记录下来,并将其曾经已经accept的编号最大的提案Proposal(myid,maxN,value)反馈给提议者,以向提议者展示自己支持的提案意愿。其中第一个参数myid表示表决者Acceptor的标识id,第二个参数表示其曾接受的提案的最大编号maxN,第三个参数表示该提案的真正内容value。当然,若当前表决者还未曾accept过任何提议,则会将Proposal(myid,null,null)反馈给提议者。
c> 在prepare阶段N不可能等于maxN。这是由N的生成机制决定的。要获得N的值,其必定会在原来数值的基础上采用同步锁方式增一。
接受阶段
1>当提议者(Proposer)发出prepare(N)后,若收到了超过半数的表决者(Accepter)的反馈,那么该提议者就会将其真正的提案Proposal(myid,N,value)发送给所有的表决者。
2>当表决者(Acceptor)接收到提议者发送的Proposal(myid,N,value)提案后,会再次拿出自己曾经accept过的提议中的最大编号maxN,或曾经记录下的prepare的最大编号,让N与它们进行比较,若N大于等于这两个编号,则当前表决者accept该提案,并反馈给提议者。若N小于这两个编号,则表决者采取不回应或回应Error的方式来拒绝该提议。
3>若提议者没有接收到超过半数的表决者的accept反馈,则重新进入prepare阶段,递增提案号,重新提出prepare请求。若提议者接收到的反馈数量超过了半数,则其会向外广播两类信息:
a>向曾accept其提案的表决者发送“可执行数据同步信号”,即让它们执行其曾接收到的提案;
b>向未曾向其发送accept反馈的表决者发送“提案 + 可执行数据同步信号”,即让它们接受到该提案后马上执行。
四、Paxos算法的活锁问题
前面所述的Paxos算法在实际工程应用过程中,根据不同的实际需求存在诸多不便之处,所以也就出现了很多对于基本Paxos算法的优化算法,以对Paxos算法进行改进,例如,Multi Paxos、Fast Paxos、EPaxos。
例如,Paxos算法存在“活锁问题”,Fast Paxos算法对Paxos算法进行了改进:其只允许一个进程处理写请求,解决了活锁问题。
Fast Paxos算法只允许一个进程提交提案,即其具有对N的唯一操作权。该方式解决了“活锁”问题
活锁简单例子
如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求......T2可能永远等待。
T2再不断的获取锁,我们称此现象为活锁。活锁不像死锁,它有时能够自己解开。预防活锁最简单的方法就是采用先来先服务法。