通过系统设计来理解Paxos算法

基础paxos算法的解决场景

paxos用来解决一致性问题,关于什么是一致性问题这里不详细展开说。可以用下面的场景示例来表达。比如我们需要使用paxos来设计一个如下的系统

  • 需要设计一个系统来存储变量var的值。
    • 系统内部由多个acceptor组成,负责存储var变量的值,var可以是任意二进制数据
    • 系统外部存在多个proposer机器(供客户端调用)可以任意并发调用api来向系统提交不同的var变量
  • 系统需要保证var的取值保证一致性
    • 如果var的取值没有被确定,则var的初始值为null
    • 一旦var的取值被确定,则不可以被更改,并且可以一直获取到var的值
  • 系统需要有容错性
    • 可以容忍任意的proposer机器出现故障
    • 可以容忍半数以下的acceptor机器出现故障(整套系统采用少数服从多数,所以半数以下出现故障,总体上剩余的还是多数)

为了设计一个上面这样的系统,首先我们从简单的地方开始。先假设系统中只有一个acceptor运行。这样所有的proposer只要能够以互斥的方式取得acceptor的访问权(写var值的权限),就可以设置var的值。那么可以如下这样做

方案一

基于互斥访问权的acceptor的实现

  • Acceptor保存变量var和一个互斥锁lock
  • prepare()
    • 加互斥锁,对var变量加上互斥访问权,并且返回var当前的值。如果失败返回error
  • accept(value)
    • 如果已经加锁并且var值没有被设置,那么将var值设置为value
  • release()
    • 释放互斥访问权

基于互斥访问权的proposer的实现

  • 第一阶段:通过prepare获取互斥访问权和当前var的取值。如果不能,则返回error(因为锁已经被别人占用)
  • 第二阶段:根据当前var值的不同,选择不同的执行策略
    • var值为null,则通过accept向acceptor设置自己提交的value值
    • var值为一个确定值,则通过release()释放访问权,并且返回ok和value的值(因为约定一旦var值被设定后,不能再更改。所以发现var值被设置后,直接释放自己的互斥访问权)

存在问题
如果一个proposer在获取到互斥访问权后出现故障,就导致互斥访问权不会被acceptor释放,从而其他的proposer无法工作。也就是说方案一是不能容忍proposer故障的。

方案二

方案二对死锁问题的解决方法就是通过使用抢占式访问权来代替互斥访问权的方式来解决问题。通俗来说,proposer向acceptor申请访问权时会携带一个全局有序的唯一编号epoch。acceptor会存储收到的最大的epoch编号,编号大的给予访问权。对于acceptor来说,一旦收到新的也就是更大的epoch,就会让旧的,也就是小的epoch失效。
在这种原则下,新的epoch可以让旧的epoch失效,旧的epoch的proposer就无法提交数据,新的epoch可以提交数据。
为了保持一致性,不同的epoch之间采用后者认同前者的原则。也就是说一旦旧的epoch产生了确定的var值,新的epoch只能获取该值,不能提交别的值。只有var值为空,也就是说旧的epoch没有产生值的情况,新的epoch才能提交值。

基于抢占式访问权的acceptor的实现
+ acceptor存储两个值,latest_epoch,{accept_epoch,accept_value}
+ latest_epoch:当前存储的最新的epoch
+ {accept_epoch,accept_value}:当前存储的,设置了value时的epoch和value键值对
+ prepare(epoch):使用epoch来抢占当前acceptor的访问权
+ epoch比latest_epoch小:抢占失败
+ epoch比latest_epoch大:抢占成功,返回acceptor存储的{accept_epoch,accept_value}
+ accept(epoch,value):使用epoch来对var进行设置值value
+ 如果epoch小于latest_epoch,则该次请求无法生效,返回失败
+ 如果epoch大于latest_epoch,则该次请求生效,设置{accept_epoch,accept_value}为提交的参数中的epoch和value

基于抢占式访问权的proposer的实现

  • 第一阶段:调用prepare(epoch)来获取epoch的访问权和当前var的值
    • 获取到访问权:成功获取到访问权和var的值
    • 不能获取访问权:说明有更大的epoch已经获取了访问权,当前操作失效
  • 第二阶段:采用后者认同前者的原则执行
    • 第一阶段获取到var为空,则肯定旧的epoch没有生成确定的var值。通过accept(epoch,value)来提交自己的value值。成功则返回{ok,value}。失败返回error,说明acceptor的latest_epoch被更新的epoch抢占,或者acceptor故障
    • 第一阶段获取到的var值存在。说明确定性的var值已经产生,此时不能再去更改,直接返回获取到的var值也就是{ok,accepted_value}

缺点
方案二解决了proposer单点故障问题。但是仍然存在acceptor单点故障导致的系统失效

basic paxos方案

basic paxos在acceptor上与方案二是相同的。而在var的取值上,采取少数服从多数的原则。一旦一个epoch的value值被半数以上的acceptor接受,则认为var的值已经被确定为value,不再更改。

基于basic paxos方案的acceptor的实现
与方案二相同

基于basic paxos方案的proposer的实现
+ 第一阶段:选定一个epoch,向所有的acceptor发送prepare(epoch)请求获取epoch的访问权和当前var值
+ 收到半数以上acceptor的访问权和var值:执行阶段二
+ 收到半数以下的acceptor的响应:返回失败。
+ 第二阶段:需要考虑第一阶段的结果。
+ 如果第一阶段收到的var值都为空,则向获取访问权的acceptor发送accept(epoch,value)。该value由proposer自己指定。
+ 如果第一阶段收到了var值,则采用后者认同前者的原则,使用其中epoch编号最大的var值作为本次提交的value。向所有的acceptor发送accept(epoch,biggest_epoch_value),biggest_epoch_value就是之前收到的所有var值中epoch最大的var值。
+ 如果超过半数的acceptor返回成功,则认为确定的var值已经被选定。否则重新发起阶段一

paxos算法的理论证明

paxos算法具有完备的数据证明。所以其在一致性算法中的地位是卓然的,也是唯一的。但是在工程实践上,有很多的难度,所以各家的实现都是从原始的paxos出发,最后有所修改,但是否正确就很难证明了。
关于paxos的证明,可以参考最原始的lampot的论文或者是他后来写的simple paxos。但是推荐先弄明白paxos的步骤,再回头看论文其实反而好理解的多。而且也可以多看看其他的资料,原始的论文的确是比较难以理解的。

猜你喜欢

转载自blog.csdn.net/kuangzhanshatian/article/details/50600165
今日推荐