算法简介
Paxos算法是莱斯利-兰伯特在1990年提出的一种基于消息传递的、具有容错性的一致性算法,Google Chubb(分布式锁服务)的作者Mike Burrwos说过,世上只有一种一致性算法,那就是Paxos算法,其他一致性算法都是Paxos的不完整版,Paxos算法是一种公认的晦涩难懂的算法,并且工程实现上也具有很大的难度,出名的实现比如Google Chubby/ZAB/微信的PhxPaxos等
Paxos算法要解决的问题就是在分布式系统中如何就某一个协议达成一致
拜占庭将军问题
这个问题是由Paxos的作者提出的点对点通信中的基本问题,该问题说的就是"在存在消息丢失的不可靠信道上试图通过信息传递的方式达到一致性是不可能的"
Paxos算法的前提就是不存在拜占庭将军问题,即信道是安全、可靠的,集群节点间传递的消息是不会被篡改的,在工程实践中,大多数系统都是部署在一个局域网内,因此被篡改的情况很少,另一方面,由于硬件和网络原因造成的消息不完整问题,现在已经不再是问题,只需要一套简单的校验算法即可,因此,在实现工程中各个服务间消息传递过程可以认为不存在拜占庭将军问题
一般情况下,分布式系统中各个节点间采用两种通讯模型:"共享内存","消息传递",而Paxos是基于消息传递通讯模型的
算法描述
三个角色
在Paxos算法中有三个角色,分别具有三种不同的行为,更多时候,一个进程可能同时充当着多种角色
-
Proposer:提案(Proposal)的提议者;
-
Acceptor:提案的(accept)表决者,即是否承认、接受该提案,超过半数接受,该提案被认定为"选定";
-
Learners:提案的学习者,当提案被选定后,其会同步并执行提案,说白一点,他就是那个没有接受提案,但提案最后通过被迫接受的那个;
一个提案的表决者会存在多个,但在一个集群中,提议者是可能存在多个的,不同的提议者给提出不同的提案,而一致性算法则可以保证以下几点
-
没有提案被提出则不会有提案被选定
-
每个提案者在提出提案时都会为该提案指定一个具有全局唯一性的、递增的提案编号N,在整个集群中时唯一的
-
每个表决者在表决某提案后,会将该提案的编号N记录在本地,这样每个表决者中保存的以及被表决的提案中会存在一个编号最大的提案,其编号架设为maxN,每个表决者仅会表决接受编号大于自己本地maxN的提案
-
在众多提案中,最终只有一个提案被选定
-
一旦一个提案被选定,则其他服务器会主动同步该提案到本地
算法过程描述
Paxos算法的执行过程划分为两个阶段:"准备阶段","接受阶段"
一、准备(prepare)阶段
-
提议者准备提交一个编号为N的提案,其首先向所有的表决者发起 prepare(N)请求,用于试探集群是否支持该编号的提议
-
每个表决者Acceptor中都保存着自己曾经accept(表决,接受)过的提议中的最大编号maxN,当一个表决者接收到其他主机发送来的prepare(N)请求时,其会比较N与maxN的值,若N的值小于等于maxN,则说明该提议已经过时,当前表决者采用不回应或者回应Error的方式来拒绝该prepare请求,若N大于maxN.则说明该提议是可以接受的,当前表决者会将其曾经接受的编号最大的提案Proposal(mid,manN,value)反馈给提议者,以向提议者展示自己支持的提案意愿,其中第一个参数mid表示该决策者Acceptor的标识id,第二个参数表示期曾接受提案的最大编号maxN,第三个参数表示该提案的真正内容value,当然,若当前表决者还未曾表决、接受过任何提议,则会将Proposal(mid,null,null)反馈给提议者
二、表决(accept)阶段
-
当提议者发出prepare(N)请求后,若收到超过半数的表决者的接受(Acceptor)的反馈,那么该提议者就会将其真正的提案Proposal(N,value)发送给所有的表决者
-
当表决者(accpetor)接受接受到提议者发送的Proposal(N,value)提案后,会再次拿出自己曾经接受(accept)过的提议的最大编号maxN,以及曾经反馈过的prepare的最大编号,让N与它们进行比较,若N大于这两个编号,则当前表决者接受(accept)这个提案,并反馈给提议者,若N不大于这两个编号,则表决者采取不回应或者回应Error的方式来拒绝该提议
-
算法举列
假设我们有三台主机,他们要从中间选取一个Leader,这三台主机在不同的时间分别充当提案的提议者Proposer、表决者Acceptor、以及学习者Learnor三种不同的角色
这里首先我们介绍一下高举列的前提:
每个tiyizheProposer都想提议自己当Leader,假设三个Proposer-1、Proposer-2、Proposer-3提议的提案初始编号分别为20、10、30,每个提议者都要将提案发送给所有的表决者Acceptor,为了便于理解,因为我们只有三个主机,假设我们就发送给两个表决者即可(已经超过半数),
Proposer-1 发送给Acceptor-1和Acceptor-2、
Proposer-2 发送给Accrptor-2 和Acceptor-3、
Proposer-3 发送给Acceptor-2 和Acceptor-3
于是就有下面这一幕出现了 :
历经下面两个阶段,选出Leader:(这里我们以金龟婿上门提亲为列)
假设 Acceptor-1:哥哥、Acceptor-2:爸爸、Acceptor-3:爷爷
图中,金龟婿1第一个对女朋友家的三个具有决定权的人物的发起了请求:Proposer-1
-
由于之前没人来上门提亲,也不清楚行情,哥哥和爸爸直接就响应了,收下了金龟婿的10W礼金,并做了礼物响应
-
(Acceptor-1和Acceptor-2分别返回(server1-id,null,null),(server2-id,null,null),并将提议编号20记录)
可能是女孩太漂亮,紧接着第二个追求者也上门提亲了:Proposer-2
-
这次哥哥出门约妹去了,金龟婿分别拜访了爸爸和爷爷,好家伙,爷爷也是不知道行情怎么样,10W就把孙女给卖了,好在爸爸在之前已经有了这方面的经验,心里想到,老子手里捏着一个20W的礼金,会卵你这个10W的,选择了不予理会,金龟婿2吃了爸爸的闭门羹,只收到了爷爷一个人的响应,闷闷不乐的离开了女方家
-
Proposer-2发来提案,Acceptor-2查询发现本地提案号大于请求的提案号不做响应,但是由于Acceptor3没有接受过任何提案,所以直接接受了请求,并返回(server3-id,nill,null),然后将提案号记录在了本地,Proposer2只收到了一个反馈,没有超过半数
可能是从众,也可能是那个女孩是真的乖巧,第三个金龟婿如约而至的上门提亲:Proposer-3
-
哥哥由于比较年轻,约的妹儿有点多,还是没在家,这次还是爸爸和爷爷分别接待的,第三个金龟婿人虽然丑了点,但是口气就不得了了啊,张口就承诺了30W礼金,爸爸听的眼睛都瞪大了,渐渐的发现面前的这个面目全非的男人的面容也变的俊俏了起来,想把之前收下的20W礼金退了的想法开始在脑海中浮现,加之酒一喝菜一吃,牙齿一咬,便一口答应把之前的礼金都退了,和面前这个俊俏的男人结为亲家,爷爷那边,如法炮制般的手段便得到了爷爷的响应,金龟婿3收到了两个响应,兴高采烈的踏出女方家门
-
Proposer3发来提案,由于受理的两个决策者发现本地的提案号均小于请求的提案号,故而接受了请求,并用请求的提案号覆盖了本地的提案号,并分别返回了((server2-id,null,null),(server3-id,null,null))
-
-
Proposer2由于没有获得超过半数的反馈,于是对自身的提案号进行递增,再次对ACceptor-2/-3发起请求,直到提案号大于Acceptor-2/-3的提案号,收到超过半数的反馈(两个),两个表决者接受了请求,并用请求的提案号覆盖了本地的提案号,并分别返回了((server2-id,null,null),(server3-id,null,null))
由于Acceptor-2/-3已经通过了Proposer-2的提案,并超过了半数,server2立马成为了Leader,选举状态结束,server2会发布广播给所有的Learner,通知他们来同步数据,同步完成后集群进入服务状态