Paxos算法(协议)基本原理

Paxos

目标:复制日志

Paxos算法(协议)基本原理_第1张图片

  • 复制日志 => 复制状态机

    • 所有服务器按照相同的顺序执行相同的命令
  • 一致性模型确保正确的日志复制
  • 只要大部分服务器是运行的系统就能工作
  • 失败的模型:失败-停止(非拜占庭),延迟/丢失信息

Basic Paxos

  • Proposers:

    • 活动的:提出特定的值被选中
    • 处理客户请求
  • Acceptors:

    • 被动的:响应消息给proposers
    • 回应达成共识的投票
    • 保存选中的值
    • 想知道哪个值被选中
    • 每个服务器都包含proposer和acceptor

一个Acceptor
Paxos算法(协议)基本原理_第2张图片
Q:如果acceptor挂掉了怎么办?
A:多个acceptors,选中被大多数acceptors的值

问题:平分投票
Paxos算法(协议)基本原理_第3张图片
Q:Acceptor只接受第一个到达的值?
A:如果同时提出提案,不会选中任何值,所以Acceptors必须可以选择多个不同的值

问题:选择冲突
Paxos算法(协议)基本原理_第4张图片
Q:Acceptor接受所有到达的值?
A:可能会导致选择多个值(如下图中的S3),所以一旦一个值被选中,以后的提案必须提出/选择一样的值(两阶段提交)

仍然有选择冲突
下图的流程:

  1. s1看到此时其他服务器没有接受某个值,之后提出red值
  2. s5当时也看到此时没有其他服务器没有接受某个值,就提出blue值
  3. blue很快就被s3、s4、s5接受,而s3之后也会接受red值
    Paxos算法(协议)基本原理_第5张图片
  4. s1的提案必须被终止(s3必须拒绝它)
  5. 因此提案必须有序,拒绝旧的提案

提案值

  • 每个提案有一个唯一的值

    • 大的优先级高
    • 提案者必须选择一个比之前见过的还大的提案值
  • 一个简单的方法:

    • 一个提案值包括 轮值+服务器ID
    • 每个服务器保存 maxRound
    • 为产生一个新的提案值:

      • 递增 maxRound
      • 与服务器ID关联
    • 提案者必须将 maxRound 持久化到磁盘,防止提案值在机器宕机重启后重用

两阶段方法

  • 阶段一:Prepare RPCs

    • 找到已经被选中的值
    • 阻止旧的提案
  • 阶段二:Accept RPCs

    • 请求acceptors接受值

流程
Paxos算法(协议)基本原理_第6张图片
例子
在之后提出一个提案有三种可能:

  • 1、之前的值已经被选中了

    • 1、 新的Proposer会发现它并使用它

    Paxos算法(协议)基本原理_第7张图片

    • 2、先说明一下方框内写的是什么意思,P表示Prepare,A表示Accept,1中的3表示 Round Number,1表示由s1提出,X为acceptedValue
    • 3、可以看到之后的提案了解到X已经被选中,s3、s4、s5也会选中同样的值
  • 2、之前没有值被选中,但已经被接受

    • 1、 新的Proposer会发现它并使用它
    • 2、 与前一种情况类似

    Paxos算法(协议)基本原理_第8张图片

  • 3、之前没有值被选中,也没看到值被接受

    • 1、 后来的Proposer会使用自己的值
    • 2、 旧的值会被拒绝

    Paxos算法(协议)基本原理_第9张图片

活锁
Paxos算法(协议)基本原理_第10张图片

其它

  • 只有 proposer 知道哪个值被选中了,acceptor是不知道的
  • 如果其它服务器想知道,就必须运行paxos

Multi-Paxos

  • 用日志项将 Basic Paxos 分成每一轮
  • 添加 index 参数到 Prepare 和 Accept

概述

  • 怎么选择哪个日志项来存放给定的客户请求?
  • 性能优化:

    • 使用 leader 减少 proposer 冲突
    • 消除大部分 Prepare 请求
  • 复制的完整型
  • 客户协议
  • 配置变更

选择日志项

  • 当客户传来请求:

    • 找到第一个没有被选中的日志项
    • 运行 Basic Paxos,在这个 index, propose 客户命令
    • Prepare 返回 acceptedValue?

      • Yes,结束选中 acceptedValue
      • No,选中客户命令

    Paxos算法(协议)基本原理_第11张图片

  • 当接收到请求时,服务器 S1 上的记录可能处于不同的状态,服务器知道有些记录已经被选定(1-mov,2-add,6-ret),在后面我会介绍服务器是如何知道这些记录已经被选定的。服务器上也有一些其他的记录(3-cmp),但此时它还不知道这条记录已经被选定。在这个例子中,我们可以看到,实际上记录(3-cmp)已经被选定了,因为在服务器 S3 上也有相同的记录,只是 S1 和 S3 还不知道。还有空白记录(4-,5-)没有接受任何值,不过其他服务器上相应的记录可能已经有值了。
  • 当 jmp 请求到达 S1 后,它会找到第一个没有被选定的记录(3-cmp),然后它会试图让 jmp 作为该记录的选定值。为了让这个例子更具体一些,我们假设服务器 S3 已经下线。所以 Paxos 协议在服务器 S1 和 S2 上运行,服务器 S1 会尝试让记录 3 接受 jmp 值,它正好发现记录 3 内已经有值(3-cmp),这时它会结束选择并进行比较,S1 会向 S2 发送接受(3-cmp)的请求,S2 会接受 S1 上已经接受的 3-cmp 并完成选定。这时(3-cmp)变成粗体,不过还没能完成客户端的请求,所以我们返回到第一步,重新进行选择。找到当前还没有被选定的记录(这次是记录 4-),这时 S1 会发现 S2 相应记录上已经存在接受值(4-sub),所以它会再次放弃 jmp ,并将 sub 值作为它 4- 记录的选定值。所以此时 S1 会再次返回到第一步,重新进行选择,当前未被选定的记录是 5- ,这次它会成功的选定 5-jmp ,因为 S1 没有发现其他服务器上有接受值。

注意事项:

  • 在这种方式下,单个服务器可以同时处理多个客户端请求,也就是说前一个客户端请求会找到记录 3- ,下一个客户端请求就会找到记录 4- ,只要我们为不同的请求使用不同的记录,它们都能以并行的方式独立运行。
  • 不过,当进入到状态机后,就必须以一定的顺序来执行命令,命令必须与它们在日志内的顺序一致,也就是说只有当记录 3- 完成执行后,才能执行记录 4- 。

提高效率

  • Basic Paxos 是低效的

    • 多个 proposers 冲突问题
    • 对于选中一个值需要两轮RPCs(Prepare、Accept)
  • 解决:

    • 选一个 leader

      • 在一个时刻,只有一个 Proposer
    • 消除大部分 Prepare RPCs

      • 一轮 Prepare 完整的日志,而不是单条记录。
      • 一旦完成了 Prepare,它就可以通过 Accept 请求,同时创建多条记录。这样就不需要多次使用 Prepare 阶段。这样就能减少一半的 RPCs。

领导选举

选举领导者的方式有很多,这里只介绍一种由 Leslie Lamport 建议的简单方式。

  • 让有 最高 ID 的服务器作为领导者
  • 可以通过每个服务器定期(每 T ms)向其他服务器 发送心跳消息 的方式来实现。这些消息包含发送服务器的 ID
  • 如果它们没有能收到某一具有高 ID 的服务器的心跳消息,这个间隔(通常是 2T ms)需要设置的足够长,让消息有足够的通信传递时间。所以,如果这些服务器没有能接收到高 ID 的服务器消息,然后它们会自己选举成为领导者。
  • 也就是说,首先它会从客户端接受到请求,其次在 Paxos 协议中,它会 同时扮演 proposer 和 acceptor
  • 如果机器能够接收到来自高 ID 的服务器的心跳消息,它就不会作为 leader,如果它接收到客户端的请求,那么它会 拒绝 这个请求,并告知客户端与 leader 进行通信。
  • 非 leader 服务器不会作为 proposer,只会作为 acceptor
  • 这个机制的优势在于,它不太可能出现两个 leader 同时工作的情况,即使这样,如果出现了两个 leader,Paxos 协议还是能正常工作,只是不是那么高效而已。
  • 应该注意的是,实际上大多数系统都不会采用这种选举方式,它们会采用基于 租约 的方式(lease based approach),这比上述介绍的机制要复杂的多,不过也有其优势。

减少 Prepare 的 RPC 请求

  • 为什么需要 Prepare?

    • 需要使用提议序号来 阻止 旧的提议
    • 使用 Prepare 阶段来检查已经被接受的值,这样就可以使用这些值来替代原本自己希望接受的值。
  1. 第一个问题是阻止所有的提议,我们可以通过改变提议序号的含义来解决这个问题, 将提议序号全局化,代表完整的日志 ,而不是为每个日志记录都保留独立的提议序号。这么做要求我们在完成一轮准备请求后,当然我们知道这样做会锁住整个日志,所以后续的日志记录不需要其他的准备请求。
  2. 第二个问题有点讨巧。因为在不同接受者的不同日志记录里有很多接受值,为了处理这些值,我们扩展了准备请求的返回信息。和之前一样,准备请求仍然返回 acceptor 所接受的最高 ID 的提议,它只对当前记录这么做,不过除了这个, acceptor 会查看当前请求的后续日志记录,如果后续的日志里没有接受值 ,它还会返回这些记录的标志位 noMoreAccepted 。
  3. 使用了这种领导者选举的机制,领导者会达到一个状态,每个 acceptor 都返回 noMoreAccepted ,领导者知道所有它已接受的记录。所以一旦达到这个状态,对于单个 acceptor 我们不需要再向这些 acceptor 发送准备请求,因为它们已经知道了日志的状态。
  4. 不仅如此,一旦从集群 大多数 acceptor 那获得 noMoreAccepted 返回值 ,我们就 不需要发送准备的 RPC 请求 。也就是说, leader 可以简单地发送 Accept 请求,只需要一轮的 RPC 请求。这个过程会一直执行下去,唯一能改变的就是有其他的服务器被选举成了 leader ,当前 leader 的 Accept 请求会被拒绝,整个过程会重新开始。

复制的完整性

  • 之前的复制是不完整的

    • 日志项只需要复制到大多数,我们需要复制到 全部
    • 只有 proposer 知道某个日志项被选中了,我们需要所有服务器都知道哪些日志项被选中了

解决办法:

  1. 在后台持续尝试 Accept RPCs 直到所有 acceptors 返回
  2. 追踪被选中的日志项

    1. 让acceptedProposal[i] = 无穷大,这样这个日志项就不可以被覆盖,也就是说被选中了
    2. 每个服务器还会保持一个 firstUnchosenIndex:表示未被标识选定的最小下标值
  3. proposer 告诉 acceptors 已经被选中的日志项

    1. Proposer 在 Accept RPCs 中包含它的 firstUnchosenIndex
    2. Acceptor 将选中第i个日志项,如果:

      1. i < request.firstUnchosenIndex
      2. acceptedProposal[i] == request.proposal
    3. 结果:acceptors知道大多数被选中的日志项

Paxos算法(协议)基本原理_第12张图片

该 acceptor 在 accept 之前的第6个日志项的提议序号为 3.4 ,
这时它收到一个提议序号也为 3.4 的 accepte 请求,并且 请求的 firstUnchosenIndex = 7,大于之前 3.4 所在的 6,所以将选中第6个日志项,同时该请求的 index = 8,将 3.4 的请求放到第8个日志项

这个机制无法解决所有的问题。问题在于 acceptor 可能会接收到来自于不同 proposer 的某些日志记录,这里记录 4 可能来自于之前轮次的服务器 S5 ,不幸的是这种情况下, acceptor 是无法知道该记录是否已被选定。它也可能是一个已失效过时的值。

我们知道它已经被 proposer 选定,但是它可能应该被另外一个值所取代。所以还需要多做一步。

  1. 旧 leader 的日志项

    1. Acceptor 返回它的 fristUnchosenIndex 在 Accept回答中
    2. 如果 proposer 的 firstUnchosenIndex > acceptor回答的 firstUnchosenIndex(说明 acceptor 中的某些状态不确定),proposer就会在后台发送 Success RPC
    3. Success(index, v):通知 acceptor 该日志项被选中了

      1. acceptedValue[index] = v
      2. acceptedProposal[index] = 无穷大
      3. return firstUnchosenIndex
      4. 如果需要(可能存在多个不确定的状态),Proposer 发送额外的Success RPC

客户端协议

  • 发送命令给 leader

    • 如果不知道哪个是 leader,随便选择一个服务器
    • 如果该服务不是 leader,重定向 给 leader
  • leader 直到日志项被选中并且被 leader 的状态机执行才回应客户
  • 如果请求超时(leader 挂了)

    • 客户重新发送命令给其他服务器
    • 最终重定向给新 leader
    • 给新 leader 重试请求

问题

  • 如果 laeder 在执行完命令但在回应之前挂掉

    • 该命令不能执行两次
  • 解决:客户端需要为每条命令提供一个 唯一的 id

    • 服务器会将该 id 存入日志项中
    • 状态机会记录每个客户最近执行的命令,即 最高 id 序号
    • 在执行命令之前,状态机会检查该命令如否被执行过,如果执行过:

      • 忽略
      • 返回之前执行的结果
    • 结果:只要客户端不崩溃,就能获得 exactly once 的保证,每个客户端命令仅被集群执行一次。如果客户端出现崩溃,就处于 at most once 的情况,也就是说客户端的命令可能执行,也可能没有执行。但是如果客户端是活着的,这些命令只会执行一次

配置变更
同一时刻不可以出现两个不重叠的多数派
Paxos算法(协议)基本原理_第13张图片

解决办法:

  • Leslie Lamport 建议的 Paxos 配置变更方案是用 日志 来管理这些变更,当前的配置作为日志的一条记录存储,并与其他的日志记录一同被复制同步。
  • 所以下图中 1-C1、3-C2 表示两个配置信息,其他的用来存储普通的命令。这里有趣的是,配置所使用每条记录是由它的更早的记录所决定的。
  • 这里有一个系统参数 å 来决定这个更早是多早,假设 å 为 3,我们这里有两个配置相关的记录,C1 存于记录 1 中,C2 存于记录 3 中,这也意味着 C1 在 3 条记录内不会生效,也就是说,C1 从记录 4 开始才会生效。C2 从记录 6 开始才会生效。所以当我们选择记录 1、2、3 时,生效的配置会是 C1 之前的那条配置,这里我们将其标记为 C0 。
  • 这里的 å 是在系统启动时配置好的参数,这个参数可以用来限制同时使用的配置信息,也就是说,我们是无法在 i+å 之前选择使用记录 i 中的配置的。因为我们无法知道哪些服务器使用哪些配置,也无法知道大多数所代表的服务器数量。所以如果 å 的值很小时,整个过程是序列化的,每条记录选择的配置都是不同的,如果 å 为 3 ,也就意味着同时有三条记录可以使用相同的配置,如果 å 大很多时,事情会变得更复杂,我们需要长时间的等待,让新的配置生效。也就是说如果 å 的值是 1000 时,我们需要在 1000 个记录之后才能等到这个配置生效。
    Paxos算法(协议)基本原理_第14张图片

总结

  • Basic Paxos:
  • Prepare 阶段
  • Acceptor 阶段
  • Multi-Paxos:
  • Choosing log entries
  • Leader election
  • Eliminting most Prepare requests
  • Full infomation propagation
  • 客户协议
  • 配置变更

你可能感兴趣的:(算法)