算法简介
Paxos算法是莱斯利-兰伯特1990年提出的一种基于消息传递的、具有很高容错性的一致性算法,Google Chubby的作者Mike Burrows说过,世上只有一种一致性算法,那就是Paxos算法,所有其他的一致性算法都是Paxos算法的不完整版,Paxos算法是一种公认的晦涩难懂的算法,并且工程实现上也具有很大难度,较有名的Paxos工程实现有Google Chubby、ZAB、微信的PhxPaxos等。
Paxos算法适用于解决什么问题的呢?Paxos算法要解决的问题是,在分布式系统中如何就某一个决议达成一致。
拜占庭将军问题
拜占庭是一个地方,位于现在的土耳其伊斯坦布尔,是当时罗马帝国的首都,由于当时罗马帝国国土辽阔,军队都分散在各个地方,相隔较远,当时拜占庭首府的军队高层为了使战争决议更加谨慎,规定所有重大战争决议必须在得到大多数将军们的一致同意之后才可实施,但是,首府与将军们,将军们之间只能靠信差传递信息,而信差可能是叛徒或者敌军的间谍,他们传递的消息并不可靠,最后形成的一致或不一致的结果,可能并不是将军们的真实意愿反馈,这就是著名的拜占庭将军问题。
拜占庭将军问题,是由Paxos算法作者莱斯利-兰伯特提出的点对点通信中的基本问题,该问题要说明的含义是,在存在消息丢失的不可靠的信道上试图通过消息传递的方式达到一致性是不可能的,Paxos算法的前提是不存在拜占庭将军问题的,即信道是安全的、可靠的,集群节点间传递的消息是不会被篡改的。
一般情况下,分布式系统中各个节点之间采用的两种通讯模型:共享内存和消息传递,而Paxos是基于消息传通讯模型的。
算法描述
三种角色
在Paxos算法中有三种角色,分别具有三种不同的行为,但很多时候,一个进程可能同时充当着多种角色。
- Proposer:提案(
Proposal)的提议者。 - Acceptor:提案的表决者,是否
accept该方案,只有半数以上的Acceptor接受了某提案,那么该提案才会被接收。 - Learners:提案的学习者,当提案被选定时,其要执行提案内容。
一个提案的表决者(Acceptor)会存在多个,但是在一个集群中,提议者(Proposer)也可能存在多个,不同的提议者(Proposer)会提出不同的提案,一致性算法则可以保证如下几点:
- 没有提案被提出则不会有提案被选定。
- 每个提议者在提出提案时都会首先获取到一个具有全局唯一性的、递增的提案编号
N,即在整个集群中石唯一的编号N,然后修改该编号赋予其要提出的提案。 - 每个表决者在
accept某提案之后,会将该提案的编号N记录在本地,这样每个表决者中保存的已经被accept的提案中会存在一个编号最大的提案,其编号假设为maxN,每个表决者仅会accept编号大于自己本地maxN的提案。 - 众多提案中港最终只能有一个提案被选定。
- 一旦一个提案被选定,则其他服务器会主动同步(
Learn)该提案到本地。
算法过程描述
Paxos算法的执行过程划分为两个阶段:准备阶段prepare与接受阶段accept。
prepare阶段
-
1.提议者(
Proposer)准备提交一个编号为N的提议,于是其首先向所有表决者(Acceptor)发送prepare(N)请求,用于试探集群是否支持该编号的提议。 -
2.每个表决者
(Acceptor)都保存着自己曾经accept过的提议中的最大编号maxN,当一个表决者接收到其他主机发送过来的prepare(N)请求时,其会比较N与maxN的大小关系,有以下两种情况。-
若
N小于maxN,则说明该提议已经过时,当前表决者采取不回应或者回应Error的方式来拒绝该prepare请求; -
若
N大于maxN,则说明该提议是可以接受的,当前表决者会首先将该N记录下来,并将其曾经accept的编号最大的提案Proposal(myid, maxN, value)反馈给提议者,以向提议者展示自己支持的提案意愿,其中第一个参数myid表示表决者Acceptor的标识id,第二个参数表示其曾接受的提案的最大编号maxN,第三个参数表示该提案真正内容value,当然,若当前表决者还未曾accept过任何提议,则会将Proposal(myid, null, null)反馈给提议者。 -
在
prepare阶段N不可能等于maxN,这是由N的生成机制决定的,要获得N的值,其必定会在原来数值的基础上采用同步锁方式增一。
-
accept阶段
-
1.当提议者(
Proposer)发出prepare(N)之后,若收到了超过半数的表决者(Acceptor)的反馈,那么该提议者会将其真正的提案Proposal(N, value)发送给所有的表决者。 -
2.当表决者(
Acceptor)接收到提议者发送的Proposal(N, value)提案后,会再次拿出自己曾经accept过的提议中最大编号maxN和曾经记录下的prepare的最大编号,让N与它们进行比较,若N大于等于这两个编号,则当前表决者accept该提案,并反馈给提议者。若N小于这两个编号,则表决者采取不回应或者回应ERROR的方式来拒绝该提议。 -
3.若提议者没有接收到超过半数的表决者的
accept反馈,则重新进入prepare阶段,递增提案号N,重新提出prepare请求,若提议者接收到的反馈数量超过了半数,则其会向外广播两类信息。-
向曾
accept其提案的表决者发送"可执行数据同步信息",即让它们执行其接受到的提案。 -
向未曾向其发送
accept反馈的表决者发送“提案+可执行数据同步信号”,即让它们接收到该提案后马上执行。
-
举例如下:
1.prepare阶段
2.accept阶段
Paxos算法的活锁问题
前面所述的Paxos算法在实际的工程应用过程中,根据不同的实际需求存在诸多不便之处,所以也就出现了很多对于基本Paxos算法优化的算法,以对于Paxos算法进行改进,例如:Multi Paxos、Fast Paxos、EPaxos等。
Paxos算法存在“活锁”问题,Fast Paxos算法对Paxos算法进行了改进:其只允许一个进程处理写请求,解决了活锁问题,ZAB协议是Fast Paxos的一种工业实现算法
为什么Paxos算法存在“活锁”问题呢?Paxos算法中每个进程均可提交提案,但是必须要获取到一个全局的唯一编号N,将该N值赋予提案,为了保证N的唯一性,对该N值操作就必须要放到同步锁(排他锁)中,N值就成了“竞争资源”,若一个进程为了提交提案,一直不停在申请资源N,但是每一次都没有分配给它,此时该进程就处于“活锁”状态。
活锁:是属于进程的执行态与就绪态之间进行徘徊的状态,没有被阻塞,活锁跟死锁的底层区别就是活锁具有CPU使用权,因为它可以不停地运算,是一个定时任务,FastPaxos算法只允许一个进程提交提案,即N是专门未某一个进程定制,即只有我申请N,才会给我,其他进程都不会给。
Paxos算法有两种应用
- 1、写请求,即Leader发送写提案给所有的Follower进行投票,超过半数,就接受该写请求,之后Leader发送同步信息让所有的Follower同步写请求。
- 2、Leader的选举,选举提案一开始都是毛遂自荐,自己推荐自己。
面试常问问题:
1.prepare与accept阶段工作过程差不过,为什么需要prepare过程呢?,paxos算法在设计之初为什么不直接进行accept阶段呢?
- 【1】prepare阶段是一个“征求意见”阶段,询问所有的Acceptor是否同意该提案,同意者反馈给Proposor一个ACK,该阶段向所有Acceptor发送的是一个全局唯一编号N
- 【2】accept阶段是提案执行阶段,只有当收到超过半数的ACK后才会让所有Learner同步提案。该阶段向所有Learner发送的是包含编号N的提案
- 【3】这两个阶段功能是不同的,发送的消息是不同的。若将两个阶段合并为一个阶段将无法进行“意见征求”,无法统计ACK
2.为什么在preapare阶段prososer发送的消息仅仅是一个编号N,而accept阶段发送的消息为proposal?反过来可以吗?
prepare阶段发送的消息是编号N,数据量比较小;accept阶段发送的消息是proposal,数据量可能比较大。Paxos算法之所以先发送小数据量的消息,是因为prepare阶段有可能是通不过的, 若通过大数量的消息进行“征求意见”,但最终没有通过,就会浪费网络带宽等资源。
3.如果zk客户端地读请求很多,在zk集群中是增加Follower还是增加Observer?
应该增加Observer,因为如果增加Follower会延长写请求提案投票时间,即写请求地执行效率会变低,因为投票地多了
4.提议者发送了prepare(N)之后,所有的Acceptor都已经将自己本地的maxN与该提案的N比较过了,并且提议者收到了超过半数的反馈,即(Proposal(myid, maxN, value)),为什么到发送Proposal(N,value)时还需要让Acceptor再比较一下自己本地的maxN与N呢,这重复的操作是多余的吗?
因为在该提议者发送Proposal(N,value)之前,可能有些Acceptor又接受到了其他提议者的prepare(N),或者有些Acceptor都已经将一个具有更大N的提案同步到了本地,即此时maxN肯定变大了,两次重复比较是为了判断你的提案是否已经过时了,本质就是不同的提案之间都是异步进行的,而不是同步进行的(即必须一个提案执行完成之后,第二个提案才开始发送prepare(N))。所以有可能出现在prepare(N)时超过半数的Acceptor都同意,但是到了Proposal(N,value)时可能有些Acceptor就不同意了,注意本质:不同的提案之间的执行是异步的,不相关的。
5.为什么向未曾发送accept反馈的表决者发送“提案+可执行数据同步信息”?
因为可能该提案在accept阶段还没有接收到一些表决者的反馈之前,已经有超过半数的表决者发送了accept反馈,同意了该提案,所以不需要等其他还没有发送accept反馈发送accept反馈了,或者有些表决者还没有接收到Proposal(N,value)呢,就已经超过半数的表决者同意了该提案。所以,向未曾发送accept反馈的表决者发送“提案+可执行数据同步信息”是为了防止这些表决者可能还不知道提案的内容呢。