比较raft ,basic paxos以及multi-paxos

比较raft ,basic paxos以及multi-paxos

uncle creepyuncle creepy

0. 引言

paxos,raft 之类的问题,在分布式一致性的讨论有多高频,这个已经不用赘言了,但是网上很多资料既多且杂,内容还互相矛盾,这篇文章的主要目的,就是深入浅出的来讲清楚这些协议的两个问题:

  1. 这些协议是用来干什么的
  2. 这些协议有什么区别,为何有这些区别

1. 首先来说说Paxos

作为basic paxos和multi-paxos,绕不过去的概念就是先得说说paxos,先来理理基本概念:

  1. paxos并不指代一个协议,而是一类协议的统称,比较常见的paxos类协议有:basic paxos , 以及 multi-paxos
  2. paxos的目的是为了多个参与者达成一致观点
  3. paxos基于一个原则,参与者不能阳奉阴违,一致的观点不会在传递过程中反转

paxos协议有一堆角色和限制,因为经统计87%的读者都会在阅读paxos的限制时就点X,这篇文章会直接跳过paxos的其他内容,因为这些内容并不有助于理解multi paxos和basic paxos

2. basic paxos vs multi-paxos

先说结论,basic paxos 的协议更复杂,且相对效率较低。所以现在所有的和paxos有关的协议,一定是基于multi-paxos来实现的。

2.1 从basic paxos的两阶段协议开始:

basic paxos的角色中,谨记一个原则:每个acceptor维护了自己知道的最大的协议号 vx,且每个acceptor不会同意任意一项小于等于vx的协议的prepare请求。

basic paxos是由client发起的同步过程,在两阶段返回前,client不能得到成功的返回。

  • 第一阶段a(发送prepare),proposer向acceptor提出一个协议,这里的协议可以理解为client发送过来期望多节点一起保存的一致性内容,举例:一句日志,某个配置等
  • 第一阶段b(计算协议vn),根据半数以上acceptor的返回,选择 max{va,vb,vc} = vn,这里的vx理解为这个acceptor已知的最大协议号,acceptor一旦返回了vx后,则表明:
    • acceptor在接下来的prepare请求中,会返回的vx自增1
    • acceptor不会accept任何小于vx的协议请求,只会accept大于vx的协议请求
  • 第二阶段a(发送决议好的vn),把vn发送给acceptor
  • 第二阶段b,在半数acceptor返回了成功后,再返回client成功通过协议

引用wiki上的流程图:

Client   Proposer      Acceptor     Learner
   |         |          |  |  |       |  |
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Prepare(1)
   |         |<---------X--X--X       |  |  Promise(1,{Va,Vb,Vc})
   |         X--------->|->|->|       |  |  Accept!(1,Vn)
   |         |<---------X--X--X------>|->|  Accepted(1,Vn)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

2.2 有助于理解basic paxos 协议的小细节

  1. proposer是可以有多个,所以他叫做proposer,而不叫leader
  2. proposer并不强制要发送propose到全部acceptor,也可以发送70%的acceptor,只要通过的有半数以上,就认为协议是成功的
  3. 容易看出,第二阶段的时候client仍需要等着,只有在第二阶段大半acceptor返回accepted后,client才能得到成功的信息
  4. 有一些错误场景中,proposer会互相锁住对方的递交,详细可以看wiki,这里不多阐述:Paxos (computer science)

2.3 Multi-paxos 如何让自己看起来像一阶段

multi-paxos 在basic paxos的二阶段上引入了一个机制,让自己看起来像一阶段一样,是如何做到的?让我们回顾basic paxos协议的关键内容:

  • 多个proposer
  • max{va,vb,vc} = vn

这两个关键点导致的后果就是,在一阶段得到vn后,proposer并不可以肯定,accept(vn) 命令会在多数acceptor中成功执行,一个极端的例子如下:

Client   Proposer        Acceptor     Learner
   |      |             |  |  |       |  |
   X----->|             |  |  |       |  |  Request
   |      X------------>|->|->|       |  |  Prepare(1)
   |      |<------------X--X--X       |  |  Promise(1,{null,null,null})
   |      !             |  |  |       |  |  !! LEADER FAILS
   |         |          |  |  |       |  |  !! NEW LEADER (knows last number was 1)
   |         X--------->|->|->|       |  |  Prepare(2)
   |         |<---------X--X--X       |  |  Promise(2,{null,null,null})
   |      |  |          |  |  |       |  |  !! OLD LEADER recovers
   |      |  |          |  |  |       |  |  !! OLD LEADER tries 2, denied
   |      X------------>|->|->|       |  |  Prepare(2)
   |      |<------------X--X--X       |  |  Nack(2)
   |      |  |          |  |  |       |  |  !! OLD LEADER tries 3
   |      X------------>|->|->|       |  |  Prepare(3)
   |      |<------------X--X--X       |  |  Promise(3,{null,null,null})
   |      |  |          |  |  |       |  |  !! NEW LEADER proposes, denied
   |      |  X--------->|->|->|       |  |  Accept!(2,Va)
   |      |  |<---------X--X--X       |  |  Nack(3)
   |      |  |          |  |  |       |  |  !! NEW LEADER tries 4
   |      |  X--------->|->|->|       |  |  Prepare(4)
   |      |  |<---------X--X--X       |  |  Promise(4,{null,null,null})
   |      |  |          |  |  |       |  |  !! OLD LEADER proposes, denied
   |      X------------>|->|->|       |  |  Accept!(3,Vb)
   |      |<------------X--X--X       |  |  Nack(4)
   |      |  |          |  |  |       |  |  ... and so on ...
因为其他proposer的存在,大家交替propose的结果就是所有的prepare计算得到的vn,全部中途作废,accept的动作一个都没正常执行。

显然,这样的决议过程是正确且低效的。

如何让basic paxos得以进行一阶段的递交,最最重点的地方聚焦在了一个点:假如只允许有一个proposer

multi-paxos将集群状态分成了两种:

  • 选主状态,由集群中的任意节点拉票发起选主,拉票中带上自己的vx,通过收集集群中半数以上的vx,来更新自己的vx值,得到目前集群通过的最大vx = vn
  • 强leader状态,leader对vn的演变了如指掌,每次把vn的值直接在一阶段中发送给acceptor,和basic paxos的区别:basic paxos一阶段的时候,proposer对vn的值一无所知,要依赖一阶段的结果来算vn

一些有趣的细节:为何选主的时候,半数的vx就可以确定集群最大vn?

  • 因为在vn生成过程中,必须达成通知到半数节点的目的,vn才能成功生成,所以一定有半数以上的节点知道vn
  • 选主的时候的限制恰好对应vn生成时的限制,这是一个环环相扣的证明

multi-paxos强leader状态的流程图:

Client     Leader       Acceptor     Learner
   |         |          |  |  |       |  |  --- Following Requests ---
   X-------->|          |  |  |       |  |  Request
   |         X--------->|->|->|       |  |  Accept!(N,I+1,W)(prepare)
   |         |<---------X--X--X------>|->|  Accepted(N,I+1,W)(prepared)
   |<---------------------------------X--X  Response
   |         |          |  |  |       |  |

2.4 multi-paxos 一些有趣的小细节:

  1. 流程图中没有了basic paxos的两阶段,变成了一个一阶段的递交协议:
  • 一阶段a:发起者(leader)直接告诉Acceptor,准备递交协议号为I+1的协议
  • 一阶段b:收到了大部分acceptor的回复后(图中是全部),acceptor就直接回复client协议成功写入
  • wiki中写的Accept方法,我更愿意把它当做prepare,因为如果没有半数返回,该协议在超时后会返回失败,这种情况下,I+1这个协议号并没有通过,在下个请求是仍是使用I+1这个协议号
  • 看过上一篇:raft 经典场景分析 - 知乎专栏 的同学已经发现了,multi-paxos在选定了leder后的行为和raft一模一样,那么multi-paxos和raft有什么区别呢,且看下一节分解。
  • 3. raft vs multi-paxos

    看到这里的,说明对分布式一致性的学习绝对是真爱。

    在我的上一篇文章:raft 经典场景分析 - 知乎专栏 中,已经详细讨论了raft的 选主和日志复制,而在上一节中,我们惊喜的发现multi-paxos和raft,在选定了leader状态下的行为模式一模一样,在这一节中,主要将比较它们在选主和日志复制上的行为。

    注意:raft的日志复制,等同我们在上文讨论的协议,为叙述方便,现在这一节,我们把multi-paxos的一阶段协议递交成为日志复制。

    结论也同样隐藏在我上一篇文章中:

    1. raft仅允许日志最多的节点当选为leader,而multi-paxos则相反,任意节点都可以当选leader
    2. raft不允许日志的空洞,这也是为了比较方便和拉平两个节点的日志方便,而multi-paxos则允许日志出现空洞

    这两个不同的设定反应到日志中的区别可以看如下图:

    如图,multi-paxos日志中间允许出现空洞,而multi-paxos的leader节点也会异步的查询其他节点来填补自身日志空洞

    如图:raft的典型日志,可以看出这个raft集群中,8节点的日志仍未完成且返回,只有1,3,5这三个节点有机会被重新选为leader

    深化一下刚才的理解,那就是:multi-paxos的leader只是知道最大的log index是多少,但是空洞部分,可以异步填充,而raft的leader,不但知道最大的log index是多少,也必须拥有从0到log index直接的之间日志,在被选择为leader后,raft的leader可以单向的向其他节点同步落后的日志。

    多谢 @LoopJump 指出,raft这个协议方便的地方在于:"raft不允许日志的空洞,这也是为了比较方便和拉平两个节点的日志方便 " Raft连续确认更大的一个优势是新主上任过程简单了。Multi Paxos在上任过程很复杂,不只是补全过程。我在raft经典场景这篇文章中也指出了这一点:

    1. 在一些一致性算法中,例如:Viewstamped Replication,
    即使一开始没有包含全部已提交的条目也可以被选为领导人。
    这些算法都有一些另外的机制来保证找到丢失的条目并将它们传输给新的领导人,
    这个过程要么在选举过程中完成,要么在选举之后立即开始。
    不幸的是,这种方式大大增加了复杂性。
    2. Raft 使用了一种更简单的方式来保证:也就是日志落后的候选人,
    无法被选中为Leader,这其实大大减小了选举和日志复制的相关性,
    简化了raft的算法理解难度。
    

    其实这点也是raft把log replication 和 选主这两个过程的有机结合,大大简化了raft协议的理解难度,大家可以参考一下。

    4. 资料

    paxos的wiki,有史以来学习paxos最好的地方:Paxos (computer science)

    raft的主页,比paxos容易理解所以我是先看的raft再来学习paxos:Raft Consensus Algorithm

    看完上面两个,不仿看看google在paxos上的工程实践:static.googleusercontent.com

    16 条评论