paxos算法详解

目的


Paxos算法的目的是为了解决分布式环境下一致性的问题。
多个节点并发操纵数据,如何保证在读写过程中数据的一致性,并且解决方案要能适应分布式环境下的不可靠性(系统如何就一个值达到统一)
paxos协议用来解决的问题可以用一句话来简化:

    将所有节点都写入同一个值,且被写入后不再更改。

基本概念


两个操作

 1. Proposal Value:提议的值;
 2. Proposal Number:提议编号,可理解为提议版本号,要求不能冲突;

三个角色

 1. Proposer:提议发起者。Proposer 可以有多个,Proposer 提出议案(value)。所谓 value,可以是任何操作,比如“设置某个变量的值为value”。不同的 Proposer 可以提出不同的 value,例如某个Proposer 提议“将变量 X 设置为 1”,另一个 Proposer 提议“将变量 X 设置为 2”,但对同一轮 Paxos过程,最多只有一个 value 被批准。
 2. Acceptor:提议接受者;Acceptor 有 N 个,Proposer 提出的 value 必须获得超过半数(N/2+1)的 Acceptor批准后才能通过。Acceptor 之间完全对等独立。
 3. Learner:提议学习者。上面提到只要超过半数accpetor通过即可获得通过,那么learner角色的目的就是把通过的确定性取值同步给其他未确定的Acceptor。
 

角色分工参与决策
Proposer提出提案,提案:[编号Id,提议的Value]
Acceptor接收提案,批准/拒绝提案,当提案被大多数的Acceptor(Quorum)批准后即为被选定的提案(Chosen)
Learner学习(Learn)最新被选定的提案×

 
流程


一句话说明是:

    proposer将发起提案(value)给所有accpetor,超过半数accpetor获得批准后,proposer将提案写入accpetor内,最终所有accpetor获得一致性的确定性取值,且后续不允许再修改。

 1. 准备阶段(占坑阶段)
 1.第一阶段A:Proposer选择一个提议编号n,向所有的Acceptor广播Prepare(n)请求。
2.第一阶段B:Acceptor接收到Prepare(n)请求,若提议编号n比之前接收的Prepare请求都要大,则承诺将不会接收提议编号比n小的提议,并且带上之前Accept的提议中编号小于n的最大的提议,否则不予理会。
 2. 接受阶段(提交阶段)
1. 第二阶段A:整个协议最为关键的点:Proposer得到了Acceptor响应
 1.如果未超过半数accpetor响应,直接转为提议失败;
 2.如果超过多数Acceptor的承诺,又分为不同情况:
  - 如果所有Acceptor都未接收过值(都为null),那么向所有的Acceptor发起自己的值和提议编号n,记住,一定是所有Acceptor都没接受过值;
  - 如果有部分Acceptor接收过值,那么从所有接受过的值中选择对应的提议编号最大的作为提议的值,提议编号仍然为n。但此时Proposer就不能提议自己的值,只能信任Acceptor通过的值,维护一但获得确定性取值就不能更改原则;
2. 第二阶段B:Acceptor接收到提议后,如果该提议版本号不等于自身保存记录的版本号(第一阶段记录的),不接受该请求,相等则写入本地。



整个paxos协议过程看似复杂难懂,但只要把握和理解这两点就基本理解了paxos的精髓:
-  理解第一阶段accpetor的处理流程:如果本地已经写入了,不再接受和同意后面的所有请求,并返回本地写入的值;如果本地未写入,则本地记录该请求的版本号,并不再接受其他版本号的请求,简单来说只信任最后一次提交的版本号的请求,使其他版本号写入失效;

- 理解第二阶段proposer的处理流程:未超过半数accpetor响应,提议失败;超过半数的accpetor值都为空才提交自身要写入的值,否则选择非空值里版本号最大的值提交,最大的区别在于是提交的值是自身的还是使用以前提交的。

例子



看这个最简单的例子:1个processor,3个Acceptor,无learner。

目标:proposer向3个aceptort 将name变量写为v1。

- 第一阶段A:proposer发起prepare(name,n1),n1是递增提议版本号,发送给3个Acceptor,说,我现在要写name这个变量,我的版本号是n1
- 第一阶段B:Acceptor收到proposer的消息,比对自己内部保存的内容,发现之前name变量(null,null)没有被写入且未收到过提议,都返回给proposer,并在内部记录name这个变量,已经有proposer申请提议了,提议版本号是n1;
- 第二阶段A:proposer收到3个Acceptor的响应,响应内容都是:name变量现在还没有写入,你可以来写。proposer确认获得超过半数以上Acceptor同意,发起第二阶段写入操作:accept(v1,n1),告诉Acceptor我现在要把name变量协议v1,我的版本号是刚刚获得通过的n1;
- 第二阶段B:accpetor收到accept(v1,n1),比对自身的版本号是一致的,保存成功,并响应accepted(v1,n1);
结果阶段:proposer收到3个accepted响应都成功,超过半数响应成功,到此name变量被确定为v1。

三军问题



1. 参谋1发起提议,派通信兵带信给3个将军,内容为(编号1);
2. 3个将军收到参谋1的提议,由于之前还没有保存任何编号,因此把(编号1)保存下来,避免遗忘;同时让通信兵带信回去,内容为(pok);
3. 参谋1收到至少2个将军的回复,再次派通信兵带信给3个将军,内容为(编号1,进攻时间1);
4. 3个将军收到参谋1的时间,把(编号1,进攻时间1)保存下来,避免遗忘;同时让通信兵带信回去,内容为(aok);
5. 参谋1收到至少2个将军的(aok)内容,确认进攻时间已经被大家接收;
6. 参谋2发起提议,派通信兵带信给3个将军,内容为(编号2);
7. 3个将军收到参谋2的提议,由于(编号2)比(编号1)大,因此把(编号2)保存下来,避免遗忘;又由于之前已经接受参谋1的提议,因此让通信兵带信回去,内容为(pok,编号1,进攻时间1);
8. 参谋2收到至少2个将军的回复,由于回复中带来了已接受的参谋1的提议内容,参谋2因此不再提出新的进攻时间,接受参谋1提出的时间;

1. 参谋1发起提议,派通信兵带信给3个将军,内容为(编号1);
2. 3个将军的情况如下
    a.将军1和将军2收到参谋1的提议,将军1和将军2把(编号1)记录下来,如果有其他参谋提出更小的编号,将被拒绝;同时让通信兵带信回去,内容为(pok);
    b.负责通知将军3的通信兵被抓,因此将军3没收到参谋1的提议;
3. 参谋2在同一时间也发起了提议,派通信兵带信给3个将军,内容为(编号2);
4. 3个将军的情况如下
    a.将军2和将军3收到参谋2的提议,将军2和将军3把(编号2)记录下来,如果有其他参谋提出更小的编号,将被拒绝;同时让通信兵带信回去,内容为(pok);
    b.负责通知将军1的通信兵被抓,因此将军1没收到参谋2的提议;
5. 参谋1收到至少2个将军的回复,再次派通信兵带信给有答复的2个将军,内容为(编号1,进攻时间1);
6. 2个将军的情况如下
    a.将军1收到了(编号1,进攻时间1),和自己保存的编号相同,因此把(编号1,进攻时间1)保存下来;同时让通信兵带信回去,内容为(aok);
    b.将军2收到了(编号1,进攻时间1),由于(编号1)小于已经保存的(编号2),因此让通信兵带信回去,内容为(ano);
7. 参谋2收到至少2个将军的回复,再次派通信兵带信给有答复的2个将军,内容为(编号2,进攻时间2);
8. 2个将军的情况如下 
    a.将军2收到了(编号2,进攻时间2),和自己保存的编号相同,因此把(编号2,进攻时间2)保存下来,同时让通信兵带信回去,内容为(aok);
    b.将军3收到了(编号2,进攻时间2),和自己保存的编号相同,因此把(编号2,进攻时间2)保存下来,同时让通信兵带信回去,内容为(aok);
9. 参谋2收到至少2个将军的(aok)内容,确认进攻时间已经被多数派接受;
10. 参谋1只收到了1个将军的(aok)内容,同时收到一个(ano);参谋1重新发起提议,派通信兵带信给3个将军,内容为(编号3);
11. 3个将军的情况如下
    a.将军1收到参谋1的提议,由于(编号3)大于之前保存的(编号1),因此把(编号3)保存下来;由于将军1已经接受参谋1前一次的提议,因此让通信兵带信回去,内容为(pok,编号1,进攻时间1);
    b.将军2收到参谋1的提议,由于(编号3)大于之前保存的(编号2),因此把(编号3)保存下来;由于将军2已经接受参谋2的提议,因此让通信兵带信回去,内容为(pok,编号2,进攻时间2);
    c.负责通知将军3的通信兵被抓,因此将军3没收到参谋1的提议;
12. 参谋1收到了至少2个将军的回复,比较两个回复的编号大小,选择大编号对应的进攻时间作为最新的提议;参谋1再次派通信兵带信给有答复的2个将军,内容为(编号3,进攻时间2);
13. 2个将军的情况如下 
    a.将军1收到了(编号3,进攻时间2),和自己保存的编号相同,因此保存(编号3,进攻时间2),同时让通信兵带信回去,内容为(aok);
    b.将军2收到了(编号3,进攻时间2),和自己保存的编号相同,因此保存(编号3,进攻时间2),同时让通信兵带信回去,内容为(aok);
14. 参谋1收到了至少2个将军的(aok)内容,确认进攻时间已经被多数派接受;

总结


第一种情况:Proposer提议正常,未超过accpetor失败情况


问题:还是上面的例子,如果第二阶段B,只有2个accpetor响应接收提议成功,另外1个没有响应怎么处理呢?

处理:proposer发现只有2个成功,已经超过半数,那么还是认为提议成功,并把消息传递给learner,由learner角色将确定的提议通知给所有accpetor,最终使最后未响应的accpetor也同步更新,通过learner角色使所有Acceptor达到最终一致性。

第二种情况:Proposer提议正常,但超过accpetor失败情况


问题:假设有2个accpetor失败,又该如何处理呢?

处理:由于未达到超过半数同意条件,proposer要么直接提示失败,要么递增版本号重新发起提议,如果重新发起提议对于第一次写入成功的accpetor不会修改,另外两个accpetor会重新接受提议,达到最终成功。

情况再复杂一点:还是一样有3个accpetor,但有两个proposer。


情况一:proposer1和proposer2串行执行


proposer1和最开始情况一样,把name设置为v1,并接受提议。
proposer1提议结束后,proposer2发起提议流程:
第一阶段A:proposer1发起prepare(name,n2)

第一阶段B:Acceptor收到proposer的消息,发现内部name已经写入确定了,返回(name,v1,n1)

第二阶段A:proposer收到3个Acceptor的响应,发现超过半数都是v1,说明name已经确定为v1,接受这个值,不在发起提议操作。


情况二:proposer1和proposer2交错执行


proposer1提议accpetor1成功,但写入accpetor2和accpetor3时,发现版本号已经小于accpetor内部记录的版本号(保存了proposer2的版本号),直接返回失败。

proposer2写入accpetor2和accpetor3成功,写入accpetor1失败,但最终还是超过半数写入v2成功,name变量最终确定为v2;

proposer1递增版本号再重试发现超过半数为v2,接受name变量为v2,也不再写入v1。name最终确定还是为v2


情况三:proposer1和proposer2第一次都只写成功1个Acceptor怎么办


都只写成功一个,未超过半数,那么Proposer会递增版本号重新发起提议,这里需要分多种情况:

1. 3个Acceptor都响应提议,发现Acceptor1{v1,n1} ,Acceptor2{v2,n2},Acceptor{null,null},Processor选择最大的{v2,n2}发起第二阶段,成功后name值为v2;
2. 2个Acceptor都响应提议,
1.如果是Acceptor1{v1,n1} ,Acceptor2{v2,n2},那么选择最大的{v2,n2}发起第二阶段,成功后name值为v2;
2.如果是Acceptor1{v1,n1} ,Acceptor3{null,null},那么选择最大的{v1,n1}发起第二阶段,成功后name值为v1;
3.如果是Acceptor2{v2,n2} ,Acceptor3{null,null},那么选择最大的{v2,n2}发起第二阶段,成功后name值为v2;
3. 只有1个Acceptor响应提议,未达到半数,放弃或者递增版本号重新发起提议

可以看到,都未达到半数时,最终值是不确定的!


其他问题


Paxos议案ID生成算法

在Google的Chubby论文中给出了这样一种方法:假设有n个proposer,每个编号为ir[0,n),proposal编号的任何值s都应该大于它已知的最大值,并且满足:

    s%n=ir =>  s=m*n+ir
    proposer已知的最大值来自两部分:proposer自己对编号自增后的值和接收到acceptor的拒绝后所得到的值。

例:以3个proposer P1、P2、P3为例,开始m=0,编号分别为0,1,2。
1. P1提交的时候发现了P2已经提交,P2编号为1 >P1的0,因此P1重新计算编号:new P1 = 1*3+1 = 4;
2. P3以编号2提交,发现小于P1的4,因此P3重新编号:new P3 = 1*3+2 = 5。

活锁


当某一proposer提交的proposal被拒绝时,可能是因为acceptor承诺返回了更大编号的proposal,因此proposer提高编号继续提交。 如果2个proposer都发现自己的编号过低转而提出更高编号的proposal,会导致死循环,这种情况也称为活锁。

比如说当此时的 proposer1提案是3,proposer2提案是4,但acceptor承诺的编号是5,那么此时proposer1,proposer2都将提高编号假设分别为6,7,并试图与accceptor连接,假设7被接受了,那么提案5和提案6就要重新编号提交,从而不断死循环。

解决方案是Proposer失败之后给一个随机的等待时间,这样就减少同时请求的可能。/选取一个主Proposer,只有主Proposer才能提出提案


Learner学习被选定的value

序号方案优点缺点
方案一Acceptor接受了一个提案,就将该方案发送给所有的LearnerLeader可以快速获取被选定的value通信次数过多
方案二Acceptor接受了一个提案,就将该方案发送给主Learner,主Learner通信次数减少单点问题(主Learner出现问题)
方案三Acceptor接受了一个提案,就将该方案发送给Learner集合,Learner集合再通知其他Learner集合中Learner个数越多,可靠性越高网络通信复杂度越高

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值