Paxos 学习笔记1 - Basic Paxos

Paxos 学习笔记1 - Basic Paxos

图片来自 John Ousterhout 的 Raft user study 系列课程

共识问题是在复制状态机的背景下被提出的

复制状态机

复制状态机是指一组状态机相同的节点,只要它们接收到的指令顺序相同,那么就会表现出相同的行为,并产生相同的结果,因此理想的情况下,即使有些机器崩溃,剩下的机器也能提供服务(只要大多数服务器还活着),服务器集群形成一个高可用的状态机

复制状态机在分布式系统中被用于解决很多容错的问题

  • 例如,大规模的系统中通常都有一个集群领导人
    • 像 GFS、HDFS 和 RAMCloud
  • 典型应用就是用独立的复制状态机管理领导选举、存储配置信息并在领导人宕机的情况下保证集群存活
    • 比如 Chubby 和 ZooKeeper

复制状态机的实现手段:replicated log

  • 为了实现复制状态机,集群不仅要对需要执行的单条指令达成共识,还要对指令的执行顺序达成共识
    • 由共识模块来保证,这也就是我们要使用到 Paxos 等共识算法的地方
  • replicated log 是一种先写日志(Write-Ahead Log),每条日志项都是状态机要执行的指令
  • 节点会按日志中的指令顺序去执行指令,由于集群对 replicated log 中日志的顺序达成了共识,因此确保了所有的节点共享相同的状态

模型

Paxos 中有三种角色

  • 提议者(proposer)
    • 主动的:提议值供大家选择
    • 处理客户端请求
  • 接受者(acceptor)
    • 被动的:回复提议者的消息,即投票
      • 接受不代表被选中,只有多数接受者接受了某个值,这个值才是被选中了
    • 存储被选中的值
    • 存储决策的过程
  • 学习者( learner)
    • 想要知道哪个值被选中了
      • 一开始只有提议者知道被选中的值
      • 提议者必须通过某种方式使得学习者也知道

单个进程可以担任多个角色

Paxos 采用异步网络下的 fail-stop 故障模型(非拜占庭故障)

  • 角色的处理速度未知,可能故障,可能重启,因此必须将已经选中的值持久化
    • 但只要服务器正常运作,它们的行为就是正确的
    • 与之相反是拜占庭故障,可以存在恶意节点以未知的方式运作
  • 消息的传递速度未知,可能重复,可能丢失,但消息不会被破坏

Paxos 算法由两部分组成

  • Basic Paxos
    • 只考虑在共识中就一个值达成一致
  • Multi-Paxos
    • 将多个 Basic Paxos 实例结合起来就一组值达成共识
    • 实现 replicated log 必须使用到 Multi-Paxos

Basic Paxos

Basic Paxos 解决的问题

一系列进程都可以提议(propose)值,共识算法要确保

  • 这些值中的一个被选中
  • 如果没有值被提出,那么不应该有值被选中
  • 进程可以学习到已经被选中的值

共识问题的要求

  • 安全性(safety)
    • 选中的值必须是被提议的
    • 只有一个值被选中
    • 进程不会学习到没有被选中的值
  • 活性(liveness)
    • 某些被提议的值最终会被选中,且进程最终能学习到被选中的值
    • 必须在有限时间内做出决议

单个接受者

最简单的场景是多个提议者、单个接受者,接受者接受第一个发给它的值,作为被选中的值,问题是如果接受者故障,整个系统就会不可用

多个接受者

采用多个接受者,用 quorum 的方式,即少数服从多数,决定被选中的值

如果接受者接受发给他的第一个值

最后可能没有一个值能取得多数的投票,这也就意味着我们无法保证在一轮投票中达成共识

如果接受者接受发给他的每个值

可能会有多个值被选中,违反了安全性要求

解决方案:

  • 一旦一个值被选中,后续的提议必须提出同一个值
  • 二阶段协议
    • 也就是说 S5 提出提议前,必须看看有没有值已经被选中了,如果有,他就必须放弃自己的提议,提出已经被选中的值 red

二阶段协议

单纯的二阶段协议

单纯使用二阶段协议仍然无法解决这个问题,下图里 S1 和 S5 在第一个阶段都发现没有其他的值被选中,因此提出自己的提议,但在这个时序下会有两个不同的值被选中

解决方案:必须对提议做排序,旧的提议应该被拒绝

  • 比如图中 S3 应该拒绝掉 S1 的提议

提议编号

给提议排序的手段是给每个提议加一个独一无二的编号,编号大的提议优先级高
最简单的做法就是用一个自增的整数+服务器 ID 作为编号,这个自增整数的最大值应该被持久化,防止服务器崩溃重启后复用已经使用过的编号

Basic Paxos 中使用的二阶段协议

  • Prepare 阶段——提议者广播 Prepare RPC
    • 看看有没有已经被选中的值,如果有,提议者的提议就应该使用这个值
    • 阻止那些更老的还未完成的提议被选中
  • Accept 阶段——提议者广播 Accept RPC
    • 让接受者接受某个值

Prepare 阶段具体流程

提议者
为自己的提议选择一个提议编号 n,然后将该编号广播给接受者

接受者

  • 如果接受者发现 n > minProposal
    • minProposal 是该接受者会接受的最小提议编号
    • 将 minProposal 更新为 n,这意味着两种承诺
      • 再也不会接受提议编号小于等于 n 的 Prepare 提议
      • 再也不会接受提议编号小于 n 的 Accept 提议
    • 回复给提议者当前已经接受的提议编号和值(acceptedPropsal 和 acceptedValue),如果当前没有接受任何提议,那么提议值为空

提议者

  • 当提议者收到多数接受者的回复
    • 用这些回复里提议编号最大的提议值作为自己的提议值
    • 如果所有回复的值都是空,提议者就可以使用自己的提议值

Accept 阶段具体流程

提议者
提议者广播提议编号和值(n 和 value)给接受者

接受者

  • 如果接受者发现 n >= minProposal,接受该提议
    • 更新 acceptedPropsal = minProposal = n
    • 更新 acceptedValue = value
  • 回复给提议者 minProposal

提议者

  • 当提议者收到多数接受者的回复
    • 如果有任何接受者拒绝了提议
      • 也就是他回复的编号比提议者提出的提议编号大
      • 说明提议在该轮没有被选中
      • 提议者应当重新选择一个更大的提议编号,回到 Prepare 阶段
    • 如果没有任何接受者拒绝该提议,说明该提议值被选中
      • 只有提议者知道这个值被选中了
      • 如果其他提议者想知道,必须发起他们自己的提议

🔑 为了处理接受者崩溃重启的情况,minProposal、acceptedPropsal、acceptedValue 都应该被持久化

示例

🔑 Paxos 的关键在于提议和接受阶段都必须收到多数的接受者的回复,两个多数的接受者集合是一定有交集的

先前的值已经被选中

先前的值已经被选中,说明已经有多数接受者接受了先前的值,那么新的提议者一定能在 Prepare 阶段看到先前的值,那么他就会将自己的提议改为先前的值,从而达成共识

先前的值没有被选中,但新的提议者看到了它

S5 在 Prepare 阶段看到有接受者接受了 X,他就会将自己的提议改为 X,从而达成共识

先前的值没有被选中,而且新的提议者没有看到它

即使 S5 没有看到有人已经接受了 X,他发出去的提议会封锁 S1 的提议,多数的接受者都会承诺不会接受 S1 的提议,那么 S1 的提议在该轮里无法被选中,S1 就会回到 Prepare 阶段重新提出提议

活锁

上面几种情况说明 Paxos 算法能保证安全性,但 Paxos 算法没法保证活性

提议者没有看到先前提议的情况下,当 S1 发现自己的提议没有通过,就会发起新一轮 Prepare RPC,然后就有可能又封锁了 S5 的提议,S5 又会回到 Prepare 阶段,有概率双方都轮流封锁对方的协议,导致无法达成共识

解决方案:

  • 每次重新提议前加上一个随机的时延
  • Multi-Paxos 将使用领导人选举来避免这种情况
    • 只有一个提议者就不会出现活锁了
posted @ 2022-02-26 22:31  路过的摸鱼侠  阅读(182)  评论(0编辑  收藏  举报