(三)Raft 论文精读笔记|In Search of an Understandable Consensus Algorithm

53 阅读33分钟

In Search of an Understandable Consensus Algorithm

In Search of an Understandable Consensus Alg orithm (Extended Version)
寻找⼀种易于理解的⼀致性算法(扩展版)

这篇文章完全按照原 Raft 论文的结构做的笔记,没有遗漏论文中的任何细节,但是更简洁,有更清晰的分点划分,也加入了部分自己的理解。

1. 背景(Abstract & Introduction)

⼀致性算法,或者说共识算法,让⼀组机器像⼀个整体⼀样⼯作,即使其中⼀些机器出现故障也能够继续⼯作。

Raft 是⼀种为了管理复制⽇志的⼀致性算法。

它将⼀致性算法分解成了⼏个关键模块:领导⼈选举、⽇志复制和安全性。同时它通过更强的⼀致性来减少状态机的数量

总之,对比传统的一致性算法 Paxos,Raft 更清晰易懂,易于实现。

它有几个独特特性:

  • 强领导者:例如⽇志条⽬只从领导者发送给其他的服务器,简化了对复制⽇志的管理并好理解。
  • 领导选举:用随机计时器来选举领导者,基于心跳机制实现,用于解决冲突。
  • 成员关系调整:使得集群在成员变换的时候依然可以继续⼯作。

2. 复制状态机(Replicated state machines)

一致性算法基于复制状态机

⼤规模的系统中通常都有⼀个集群领导者(Primary,论文中用的 Leader),⼀个独⽴的的复制状态机管理领导选举和存储配置信息并且在领导⼈宕机的情况下存活下来。

在之前 VMware FT 中已经学过这个,复制状态机通常都是基于复制⽇志实现

日志传递 Primary 所有操作记录,让所有 Backup 执行相同的指令序列,达到相同的状态。

图 1 :复制状态机的架构

**保证复制⽇志相同就是⼀致性算法的⼯作。**一致性模块(图中 Consensus Module)接受客户指令,生成日志,和其他 Backup 通信传递日志。

实际使用的一致性算法有以下特征:

  • 安全性:非拜占庭错误情况下,绝不会返回错误的结果
  • 可用性:只要大多数机器正常就可保证可用。
  • 不依赖时序保证一致性
  • 一条指令大多数节点可一轮 RPC 完成,小部分慢节点不影响整体性能

3. Paxos 算法的问题(What’s wrong with Paxos?)

Paxos ⾸先定义 了⼀个能够达成单⼀决策⼀致的协议,⽐如单条的复制⽇志项。这⼀⼦集叫做单决策Paxos。然后通过组合多个 Paxos 协议的实例来促进⼀系列决策的达成

Paxos 是大多数一致性算法的起点,理论上证明可行,但是有两个明显缺点:

  1. 特别难以理解。不直观、不透明。
  2. 理论可行,实际实现困难,不易于构建实践系统。

Raft 算法就是尝试克服以上缺点,替代 Paxos 的一致性算法。

4. 为了可理解性的设计(Designing for understandability)

设计 Raft 的初衷:

  • 提供⼀个完整的实际的系统实现基础:大大减少开发者的工作
  • 任何情况下都是安全的
  • ⼤多数的情况下都是可⽤的
  • ⼤部分操作必须是⾼效的
  • 可理解性:(最重要、最大挑战)保证普遍⼈都可以容易理解。
  • 能够让⼈形成直观的认识:使系统的构建者能够在现实中进⾏必然的扩展。

为了可理解性做的工作:

  • 问题分解:尽可能将问题分解成⼏个相对独⽴的、可被解决的、可解释的和可理解的⼦问题。
    Raft 算法被分成领导⼈选举、⽇志复制、安全性和⻆⾊改变⼏个部分。

  • 减少状态的数量来简化需要考虑的状态空间:使得系统更加连贯并且在可能的时候消除不确定性。
    随机化⽅法增加了不确定性,但是有利于减少状态空间数量,通过处理所有可能选择时使⽤相似的⽅法。
    使⽤随机化去简化 Raft 中领导⼈选举算法

5. Raft 共识算法(The Raft consensus algorithm)

Raft 是⼀种⽤来管理第 2 部分中描述的复制⽇志的算法。图2是这个算法的简略版,图3列举了这个算法的关键特性。

Raft 选举一个 Leader,给予管理所有复制日志的权限,由此实现一致性。

Leader 从客户接受指令,写入日志,复制到其他 Backup Server 上,在保证安全性时通知其他 Server 根据日志执行指令更新状态机。

Leader 大大简化了对复制日志的管理。leader 可以自行决定新日志写入位置,数据都从 Leader 流向其他 Server。当 Leader 宕机,其他 Server 中选举一个新 Leader。

由此 Raft 将一致性问题分解为三个子问题

  • 领导选举:旧 Leader 宕机选举新 Leader (5.2 节)

  • 日志复制:Leader 接受日志,复制到其他节点并保证一致。

  • 安全性:关键在于状态机安全(图 3):某一节点应用某个日志条目到状态机,其他节点不能在此条目应用不同指令。(5.4 节)
    此处还涉及一个额外的选举机制上的限制(5.2 节)

两张图

图2是 Raft 算法的简略版,图3列举了 Raft 算法的关键特性。

对这两张图片内容进行了全部翻译。


图2:一个关于 Raft 一致性算法的浓缩总结(不包括成员变换和日志压缩)

State (状态)

所有服务器上持久存在的:

(在响应RPCs之前已在稳定的存储上进行更新)

currentTerm服务器最后⼀次知道的任期号(初始化为 0,持续递增)
votedFor在当前获得选票的候选⼈的 Id(如果没有则为 null)
log[]⽇志条⽬集;每⼀个条⽬包含⼀个⽤户状态机执⾏的指令,和收到时的任期号

所有服务器上经常变的:

commitIndex已知的最⼤的已经被提交的⽇志条⽬的索引值
lastApplied最后被应⽤到状态机的⽇志条⽬索引值(初始化为 0,持续递增)

在领导⼈⾥经常改变的:

(选举后重新初始化)

nextIndex[]对于每⼀个服务器,需要发送给他的下⼀个⽇志条⽬的索引值(初始化为领导⼈最后索引值加⼀)
matchIndex[]对于每⼀个服务器,已经复制给他的⽇志的最⾼索引值

AppendEntries RPC(追加待同步⽇志 RPC)

由 Leader 负责调⽤来复制⽇志(5.3);也会⽤作心跳(5.2)

传入参数:

term领导⼈的任期号
leaderId领导⼈的 Id,以便于跟随者重定向请求
prevLogIndex新的⽇志条⽬紧随之前的索引值
prevLogTermprevLogIndex 条⽬的任期号
entries[]准备存储的⽇志条⽬(表示⼼跳时为空;⼀次性发送多个是为了提⾼效率)
leaderCommit领导⼈已经提交的⽇志的索引值

返回值:

term当前的任期号,⽤于领导⼈去更新⾃⼰
success跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的⽇志时为真

接收者实现:

  1. 如果 term < currentTerm 就返回 false (5.1 节)
  2. 如果⽇志在 prevLogIndex 位置处的⽇志条⽬的任期号和 prevLogTerm 不匹配,则返回 false (5.3 节)
  3. 如果现有的⽇志条⽬和新的产⽣冲突(索引值相同但是任期号不同),删除现有的和之后所有的条目 (5.3 节)
  4. 追加⽇志中尚未存在的任何新条⽬
  5. 如果 leaderCommit > commitIndex ,令 commitIndex = min(leaderCommit, 新日志条目索引)

RequestVote RPC(请求投票 RPC)

由候选⼈调⽤⽤来征集选票(5.2 节)

传入参数

term候选⼈的任期号
candidateId请求选票的候选⼈的 Id
lastLogIndex候选⼈的最后⽇志条⽬的索引值
lastLogTerm候选⼈最后⽇志条⽬的任期号

返回值

term当前任期号,以便于候选⼈去更新⾃⼰的任期号
voteGranted候选⼈赢得了此张选票时为 true

接收者实现:

  1. 如果term < currentTerm返回 false (5.2 节)
  2. 如果 votedFornull 或者为 candidateId,并且候选人的日志至少和接受者一样新,那么就给它投票(5.2 节,5.4 节)

Rules for Servers(服务器的规则)

所有服务器

  • 如果commitIndex > lastApplied,那么就 lastApplied 加一,并把log[lastApplied]应用到状态机中(5.3 节)
  • 如果接收到的 RPC 请求或响应中,任期号T > currentTerm,那么就令 currentTerm 等于 T,并切换状态为 Follower(5.1 节)

Followers(跟随者)(5.2 节):

  • 响应来自候选人和领导者的 RPC 请求
  • 如果选举超时,都没有收到现任 Leader 的AppendEntries RPC,也没有给候选人投票:自己转变成候选人。

Candidates(候选人)(5.2 节):

  • 在转变成候选人后就立即开始选举过程

    • 自增当前的任期号(currentTerm

    • 给自己投票

    • 重置选举超时计时器

    • 发送 RequestVote RPC 给其他所有服务器

  • 如果接收到大多数服务器的选票,那么就变成 Leader

  • 如果接收到来自新的 Leader 的 AppendEntries RPC,转变成 follower

  • 如果选举过程超时,再次发起一轮选举

Leader(领导人):

  • 一旦成为领导人:发送初始的空 AppendEntries RPCs(心跳)给每个服务器;在空闲期间重复发送,防止选举超时(5.2 节)

  • 如果接收到来自客户端的请求:附加条目到本地日志中,在条目被应用到状态机后响应客户端(5.3 节)

  • 如果一个 follower 最后日志条目的索引值 index ≥ nextIndex,那么:使用 AppendEntries RPC 发送从 nextIndex 开始的所有日志条目:

    • 如果成功:更新相应跟随者的 nextIndexmatchIndex

    • 如果 AppendEntries 因为日志不一致而失败,减少 nextIndex 并重试

  • 如果存在一个满足N > commitIndex的 N,并且大多数的matchIndex[i] ≥ N成立,并且log[N].term == currentTerm成立,那么令 commitIndex = N (5.3 和 5.4 节)


图 3:Raft 在任何时候都保证以上的各个特性

特性解释
选举安全对于一个给定的任期号,最多只会有一个领导人被选举出来(5.2 节)
Leader 只追加领导人绝对不会删除或者覆盖自己的日志,只会增加(5.3 节)
日志匹配特性如果两个日志在相同的索引位置的日志条目的任期号相同,那么我们就认为这个日志从头到这个索引位置之间全部完全相同(5.3 节)
领导人完全特性如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大任期号的所有领导人中(5.4 节)
状态机安全特性如果一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会提交一个不同的日志(5.4.3 节)

5.1 Raft 基础(Raft basics)

一个 Raft 集群通常包含 5 个节点,能容忍 2 个节点宕机。

Raft 集群的服务器都处于三个状态之一:

  • Leader**:只有一个,响应所有客户端请求**
  • Follower**:其余都是,不发送只响应 Leader 或 Candidate 的请求。若客户向其请求,会重定向到 Leader。**
  • Candidate**:选举新 Leader 时使用(5.2)**

图 4:服务器状态。Follower 只响应来自其他服务器的请求。如果 Follower 接收不到消息,那么他就会变成 Candidate 并发起一次选举。获得集群中大多数选票的 Candidate 将成为 Leader。在一个任期内,Leader 保持身份直到自己宕机。

如图 5,Raft 把时间分割成任意长度的任期(term) ,用连续递增整数编号,任期开始即选举。Raft 保证一个任期只有一个 Leader。

图 5:时间被划分成一个个的任期(term),每个任期开始都是一次选举。在选举成功后,领导人会管理整个集群直到任期结束。有时候选举会失败,那么这个任期就会没有领导人而结束。任期之间的切换可以在不同的时间不同的服务器上观察到。

任期编号在 Raft 算法中充当逻辑时钟,每个节点都储存当前任期号,节点之间通信会交换任期号,当一个节点:

  • 当前任期号比其他节点小,更新自己的任期号
  • Leader 或 Candidate 发现自己任期号过期,立即转为 Follower
  • 收到过期的任期号请求,拒绝请求。

节点之间通信使用远程过程调用(RPCs) ,包含两种(第7节还增加了第三种传送快照的):

  • **请求投票(RequestVote) RPCs:**Candidate 在选举期间发起(5.2)
  • 追加条目(AppendEntries)RPCs:Leader 发起,用于复制日志和心跳(5.3)

节点未及时收到 RPC 响应会重试,能并行发起 RPCs。

5.2 Leader 选举(Leader election)

  1. 刚开始所有节点都是 Follower
  • Follwer 一段时间没接收到消息即选举超时,发起新选举。
  • Leader 周期性发送**心跳包(不含日志的 AE RPC)**给所有 Follower 来维持自己地位。
  • Follwer 只要能收到 Leader 或 Candidate 的 RPC 就保持当前状态。
  1. 开始选举。Follower 自增 term(任期号)并转为 Candidate,并行向其他节点发送 RV RPC 等待给自己投票。
  • 等待时收到 Leader 的心跳,且心跳中的任期不小于自己的当前任期,则自己变为 Follower。若小于自己的任期,则拒绝并保持 Candidate。
  • 如果同时出现多个 Candidate,选票可能被瓜分,没有人得到多数选票。则等待超时后重新选举。
  • Raft 使用随机选举超时时间(例如 150-300 毫秒)防止多次无人上任。每个节点开始选举时重制超时时间。可以让多数情况只有一个节点超时,进入下一轮赢得选举。
  1. 获得多数选票的 Candidate 变为 Leader
  • 每个节点在一个任期内,按先来先服务(5.4节还有额外限制)最多为一个 Candidate 投票
  • 成为 Leader 后向其他节点发送心跳建立权威。

5.3 日志复制(Log replication)

5.3.1 Leader 日志复制流程

  • 把客户端请求指令追加到日志,然后并行发 AE RPC 给其他节点让其追加日志。
  • 在日志被其他节点安全复制后(多数节点已复制),Leader 应用该指令到状态机并返回结果给客户端。
  • 如果中途出现问题,Leader 会不断重复 AE RPC(甚至已回复客户端后)直到所有 Follower 都追加了该日志。

5.3.2 日志提交

  • 一条日志包含当前任期号一条指令,也都有一个整数索引来表明它在日志中的位置。

  • Leader 决定什么时候能把日志安全应用到状态机,这样的日志条目为已提交committed)。Raft 保证所有已提交日志都是持久化并最终被所有状态机执行。
    Leader 把日志设为已提交后,还需要通知 Follower 应用日志到状态机,这个通知通过下一次 AE RPC(也许是心跳)附加 commitIndex。论文没有提到这里具体如何实现,6.824课程中讲了。

  • 日志条目复制到大多数节点上时,就是已提交(例如图 6 的 7),且 Leader 中当前条目之前的日志也都已提交,包括其他 Leader 创建的条目(5.4)。Leader 记录最大已提交索引leaderCommit,并放进所有 AE PRCs,其他节点由此得知 Leader 已提交位置,并按日志顺序应用到自己的状态机。

图 6:日志由序号标记的条目组成。每个条目都包含创建时的任期号和一个状态机需要执行的指令。一个条目当可以安全的被应用到状态机中去的时候,就认为是可以提交了。

5.3.3 日志一致性

这样 Raft 能维持日志的一致性(也是图 3 中的日志匹配特性):

  • 在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令

  • 在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同
    追加日志的一致性检查:每次新条目 AE RPC 给 Follower,如果上一条索引任期不一致,则拒收新条目。所以一旦 AE RPC 返回成功,说明 Follower 所有日志和 Leader 相同

5.3.4 日志不一致情况

正常情况下一致性检查不会失败,能一直保持一致。但是 Leader 在未完全复制日志时宕机会使日志不一致。例如 Follower 可能没有新Leader 有的条目,也可能有新 Leader 没有的条目,或者都有,如图 7。

图 7:当一个领导人成功当选时,跟随者可能是任何情况(a-f)。每一个盒子表示是一个日志条目;里面的数字表示任期号。跟随者可能会缺少一些日志条目(a-b),可能会有一些未被提交的日志条目(c-d),或者两种情况都存在(e-f)。例如,场景 f 可能会这样发生,某服务器在任期 2 的时候是领导人,已附

5.3.5 不一致的恢复

Raft 中处理这种不一致方法是,Leader 强制 Follower 复制自己的日志,即覆盖 Follower 中所有冲突日志(安全性在5.4)。

Leader 找到最后和 Follower 一致的地方,删除 Follower 之后的冲突日志,发送自己的日志附加给 Follower。这些操作在 AE RPCs 一致性检查时完成

  • Leader 对每个 Follower 维护下一个需发送的条目索引nextIndex,在刚上任时初始化为最新日志索引加一(图 7 中 11)。

  • Follower 日志不一致则拒绝 AE PRC,Leader 减小nextIndex重试直到成功,Follower 删除冲突日志并追加 Leader 日志。日志即保持一致。
    这里可以优化,Follower 拒绝时返回冲突任期号的最早地址,Leader 下次就可以越过同任期号的冲突。但是此优化不一定有必要,因为实际很少发生。

所以 Leader 无需特殊操作就能恢复一致性,Leader 也从不会覆盖删除自己的日志(图3 Leader 只追加特性)。

日志复制机制展示了第 2 节的一致性特征:

  • 只要大部分的机器是工作的就能正常复制日志和应用保证可用性;
  • 一条指令大多数节点可一轮 RPC 完成,小部分慢节点不影响整体性能

5.4 安全性(Safety)

以上的讨论不能保证每个状态机按相同顺序执行相同指令。例如因故障缺少被提交日志的 Follower 可能被选为 Leader 并覆盖日志。

故需增加选举限制,保证图 3 中的领导人完整性,即领导人一定包含所有已提交日志条目

5.4.1 选举限制

某些一致性算法中需要额外复杂机制把缺少的日志传给 Leader。但是 Raft 保证 Leader 本来就有所有日志,所有日志都是单向从 Leader 传出去。

Raft 在等待投票时,RV PRC 包含 Candidate 的日志信息,投票人会拒绝日志没有自己新的投票请求

投票人比较最后一条日志的索引值和任期号

  • 任期号不同,则任期号大的比较新
  • 任期号相同,索引值大的(日志较长的)比较新

5.4.2 之前任期已提交的日志条目

5.3 中介绍了,只要日志条目被存在多数节点,Leader 就知道此日志在自己任期已提交。

Leader 可能在提交之前崩溃,新 Leader 不知道保存在多数节点的的条目是否提交。例如图 8,存在多数节点的老日志仍可能被覆盖。

图 8:如图的时间序列展示了为什么领导人无法决定对老任期号的日志条目进行提交。在 (a) 中,S1 是领导者,部分的复制了索引位置 2 的日志条目。在 (b) 中,S1 崩溃了,然后 S5 在任期 3 里通过 S3、S4 和自己的选票赢得选举,然后从客户端接收了一条不一样的日志条目放在了索引 2 处。然后到 (c),S5

图 8 说明即使某日志被复制到多数节点也不代表它被提交。

所以 Raft 对日志提交条件增加一个额外限制Leader 在当前任期至少有一条日志被提交(即超过半数节点复制),如图 8 中的(e)所示。而(c)中并没有提交 4 任期的日志。

所以新上任的 Leader 在接受客户写入命令前先提交一个 no-op(空命令) ,携带自己任期号的日志复制到多数节点,这样能保证选举限制成立。

这里可以认为 2 和 4 日志的复制是原子的,不会如 (c) 一样单独提交 2,要么就 2 和 4 一起提交要么就都失败。

5.4.3 安全性证明

定义:上个任期 Leader 最后提交了日志 L,B为当前任期 Leader,某个拥有 L 的节点 C。

  1. 由于 L 已提交,所以多数节点复制了 L。
  2. 由于 B 当选,所以多数节点投票给 B。
  3. 由(1)和(2)可知,B 的选民中必然含有存在 L 的节点 C。
  4. 由于选举限制(投票人会拒绝日志没有自己新的投票请求),B 当选必须要日志比 C 新
  5. 所以 B 的日志中必然含有日志 L。
  6. 由于日志匹配特性(一致性检查),B 包含 L 则 B 必定包含 L 之前的所有已提交日志
  7. 由于 Raft 保证所有节点上已提交日志顺序一致,且当前应用日志小于等于提交日志(lastApplied ≤ commitIndex),状态机按序执行应用日志。
  8. 得证:所有节点上状态机必然最终一致。图 3 中状态机安全特性成立

图 9:如果 S1 (任期 T 的领导者)提交了一条新的日志在它的任期里,然后 S5 在之后的任期 U 里被选举为领导人,然后至少会有一个机器,如 S3,既拥有来自 S1 的日志,也给 S5 投票了。

论文中使用了反证法。先假设 B 没有日志 L,推出矛盾得证假设不成立,过程差不多。

5.5 Follower 和 Candidate 崩溃(Follower and candidate crashes)

前面都是讨论 Leader 崩溃,Follower和 Candidate 崩溃后的处理方式简单的多,Raft 只需要不断重试发送 RPCs 即可,崩溃重启后再执行 RPC。

Raft 的 RPCs 都是幂等的,重试不会产生问题。如果 Follower 发现 AE RPC 中的日志已经有了,它直接忽略这个请求。

幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

5.6 时间和可用性( Timing and availability)

Raft 的要求之一就是安全性不能依赖时间:整个系统不能因为某些事件运行的比预期快一点或者慢一点就产生了错误的结果。

但可用性不可避免要依赖时间,最关键在于 Leader 选举,需要满足如下时间要求:

image.png

  • 广播时间(broadcastTime)
    一个节点并行发送 RPCs 给其他节点并接收响应的平均时间。
    应比选举超时时间小一个数量级才能保证稳定的心跳。
    广播时间大约是 0.5 毫秒到 20 毫秒,取决于存储的技术

  • 选举超时时间(electionTimeout)
    即 5.2 中选举超时时间限制。随机化使之难以瓜分选票。
    只有选举超时时间是我们自己选择的。可能需要在 10 毫秒到 500 毫秒之间。
    应比平均故障时间小几个数量级。Leader 崩溃后系统将在一个选举超时时间中不可用,此情况应很少出现。

  • 平均故障间隔时间(MTBF)
    一个节点两次故障之间的平均时间。
    大多数服务器平均故障时间在几个月甚至更长,很容易满足时间的需求。

6. 集群成员变化(Cluster membership changes)

实践中会替换那些宕机的机器或者改变复制级别,手动暂停重启易出错且会有段时间不可用,所以需要自动化配置改变,并且将其纳入到 Raft 一致性算法中。

6.1 两阶段提交:Joint Consensus

为了让配置修改机制安全,在转换的过程中同一个任期里不能够存在两个 Leader 同时当选。问题在于,一次性自动的转换所有服务器是不可能的,任何切换方法都是不安全的,所以在转换期间整个集群可能分裂成两个独立的多数(见图 10)。

图 10:直接从一种配置转到新的配置是十分不安全的,因为各个机器可能在不同时候进行转换。在这个例子中,集群配额从 3 台机器变成了 5 台。不幸的是,存在这样的一个时间点,两个不同的领导人在同一个任期里都可以被选举成功。一个是通过旧组合的多数(C_old),一个通过新组合的多数(C_new)。

为了保证安全性,配置更改必须使用两阶段方法。有些系统在第一阶段停掉旧的配置,集群就不能处理客户端请求;然后在第二阶段在启用新的配置。

在 Raft 中,集群先切换到一个过渡性配置,我们称之为 Joint Consensus联合共识,共同一致);一旦联合共识被提交,那么系统就切换到新的配置上。

Joint Consensus 是老配置和新配置的结合:

  • 日志条目被复制给集群中新、老配置的所有服务器。
  • 新、旧配置的服务器都可以成为领导人。
  • 达成一致(针对选举和提交)需要分别在两种配置上获得大多数的支持

Joint Consensus 允许独立的服务器在不影响安全性的前提下,在不同的时间进行配置转换,还可以让集群在配置转换的过程中依然响应客户端的请求。

6.2 实现细节

集群配置在复制日志中以特殊的日志条目来存储和通信。图 11 展示了配置转换的过程:

  • 当一个 Leader 接收到一个改变配置从 C-old 到 C-new 的请求,他将创建联合共识的配置(图中的 C-old,new)并存储为一个新日志条目。

  • Leader 将 C-old,new 发给所有 Follower 进行复制。

    • 如果 C-old,new 被半数以上节点同步,则此配置已提交,之后遵循 Raft 安全性机制,只有拥有 C-old,new 日志条目的服务器才有可能被选为新 Leader

    • 如果半数同步前 Leader 崩溃,新 Leader 可能有 C-old,new 也可能没有,若没有则退回老配置重试更新即可。

    • 在这一时期,C-new 不会有任何影响。

  • C-old,new 已提交后,C-old 已不会产生影响,Leader 再创建和提交 C-new 就是安全的了。

在整个过程中没有哪个时候让 C-old 和 C-new 同时产生影响,保证了安全性。

图 11:一个配置切换的时间线。虚线表示已经被创建但是还没有被提交的条目,实线表示最后被提交的日志条目。领导人首先创建了 C-old,new 的配置条目在自己的日志中,并提交到 C-old,new 中(C-old 的大多数和 C-new 的大多数)。然后他创建 C-new 条目并提交到 C-new 中的大多数。这样就不存在 C-new

6.3 问题讨论

  1. 没有存储任何的日志条目新节点加入,复制日志条目需要时间,此时无法作为提交和选举决策的节点。
    新节点设置保护期,此期间以没有投票权身份加入到集群中来,不参加选举投票和日志提交决策,直到日志同步完毕。

  2. Leader 不是新配置成员。
    Leader 在提交了 C-new 日志之后主动退位(回到 Follower 状态)。并且在复制提交 C-new 时自己不算半数之一

  3. 被移除的服务器未关闭,可能会扰乱集群。因为它们不再收到心跳,就会一直超时发起带有新任期号的选举。
    集群中节点在未达到选举超时时间前,不响应 RV RPC。即如果当前 Leader 能够在超时时间内发送心跳,Follwer 就能确认当前 Leader 存在而不响应新的投票请求。

7. 日志压缩(Log Compaction)

7.1 快照基本思路

日志不能无限增长,Snapshotting快照)是最简单的压缩方法。在快照系统中,整个系统的状态都以快照的形式写入到稳定的持久化存储中,那个时间点之前的日志全部丢弃。

增量压缩,例如日志清理或者日志结构合并树也可行,这些方法每次只对一小部分数据进行操作,分散了负载压力。首先,选择一个已经积累的大量已经被删除或者被覆盖对象的区域,然后重写那个区域还活跃的对象,之后释放那个区域。

增量压缩需要增加复杂的机制来实现,而快照总是简单操作整个数据集合,简化了这个问题。日志清除方法需要修改 Raft,但是状态机可以使用和快照相同的接口实现 LSM tree(日志结构合并树)

图 12:一个服务器用新的快照替换了从 1 到 5 的条目,快照值存储了当前的状态。快照中包含了最后的索引位置和任期号。

图 12 展示了 Raft 中快照的基本思路:

  • 每个服务器独立的创建快照,只包括已经被提交的日志。大部分由状态机将当前状态写入到快照中,也包括少量元数据:

    • lastIncludedIndex:被快照取代的最后的条目在日志中的索引值(状态机最后应用的日志)
    • lastIncludedTerm:该条目的任期号

保留这些数据是为了支持快照后第一个 AE RPC 时的一致性检查,因为这个条目需要前一日志条目的索引值和任期号。

  • 为了支持集群成员更新(第 6 节),快照中也将最后的集群配置作为最后一个状态条目存下来。一旦服务器完成一次快照,他就可以删除最后索引位置之前的所有日志和快照了。

7.2 InstallSnapshot RPC

Leader 必须偶尔通过 RPC 发送快照给一些落后的 Follower。一般发生于当 Leader 已经删除下一条需要发送给某 Follower 的日志条目的时候。例如一个运行非常缓慢的 Follower 或者新加入集群的服务器(第 6 节),这时让这个 Follower 更新到最新的状态的方式就是通过网络把快照发送给他们。

当 Follower 接收到 IS RPC 时,自己决定对于已经存在的日志该如何处理。

  • 通常快照会包含没有在接收者日志中存在的信息。此时跟随者丢弃其整个日志,全部被快照取代,即使包含与快照冲突的未提交条目。
  • 如果接收到的快照是自己日志的前面部分(由于网络重传或者错误),那么被快照包含的条目将会被全部删除,但是快照后面的条目仍然有效,必须保留

图 13:一个关于安装快照的简要概述。为了便于传输,快照都是被分成分块的;每个分块都给了跟随者生命的迹象,所以跟随者可以重置选举超时计时器。

安装快照 RPC

由 Leader 调用,将快照的分块发送给 Follower。Leader 总是按顺序发送分块。

参数解释
term领导人的任期号
leaderId领导人的 Id,以便于跟随者重定向请求
lastIncludedIndex快照中包含的最后日志条目的索引值
lastIncludedTerm快照中包含的最后日志条目的任期号
offset分块在快照中的字节偏移量
data[]原始数据
done如果这是最后一个分块则为 true
返回结果解释
term当前任期号(currentTerm),便于领导人更新自己

接收者实现

  1. 如果term < currentTerm就立即回复
  2. 如果是第一个分块(offset = 0)就创建一个新的快照
  3. 在指定偏移量写入数据
  4. 如果 done = false,则继续等待更多的数据
  5. 保存快照文件,丢弃具有较小索引的任何现有或部分快照
  6. 如果现存的日志条目与快照中最后包含的日志条目具有相同的索引值和任期号,则保留其后的日志条目并进行回复
  7. 丢弃整个日志
  8. 使用快照重置状态机(并加载快照的集群配置)

7.3 问题讨论

这种快照的方式背离了 Raft 的强 Leader 原则,因为 Follower 可以在 Leader 不知情情况下创建快照,但是这是值得的。领导人的存在,是为了解决在达成一致性的时候的冲突,创建快照的时候一致性已经达成,不存在冲突了,所以没有领导人也是可以的。数据依然是从领导人传给跟随者,只是跟随者可以重新组织他们的数据。

而只有 Leader 创建快照,发送给所有的 Follower 的方案有三个问题:

  • 浪费网络带宽并且延缓了快照处理的时间,Follower 已有快照所需信息显然更经济。
  • Leader 的实现会更加复杂。例如需要发送快照的同时并行的将新的日志条目发送给跟随者,这样才不会阻塞新的客户端请求。

还有两个问题影响快照性能:

  1. 什么时候应该创建快照?过于频繁会浪费大量的磁盘带宽和其他资源;频率太低要承受耗尽存储容量的风险,也增加了从日志重建的时间。
    日志大小达到一个固定大小的时候就创建一次快照。如果这个阈值设置的显著大于期望的快照的大小,那么快照对磁盘压力的影响就会很小了。

  2. 写入快照需要花费显著的一段时间,并且我们还不希望影响到正常操作,如何处理?
    写时复制的技术,这样新的更新就可以被接收而不影响到快照。具有函数式数据结构的状态机天然支持这样的功能。另外,操作系统的写时复制技术的支持(如 Linux 上的 fork)可以被用来创建完整的状态机的内存快照(我们的实现就是这样的)。

写时复制的技术在 GFS 论文中快照部分有提到,类似假创建、惰性创建。master 记录操作到磁盘后创建拷贝文件的元数据,其与原文件指向相同 chunk。即只创建了一个元数据指针。此时对原文件和副本的读实际都是同一个 chunk。一旦发生写操作,无论原文件还是拷贝文件的修改都会导致在本地机器创建一个新chunk,并修改变更文件的元数据信息

8. 客户端交互( Client interaction)

这一节将介绍客户端是如何和 Raft 进行交互的,包括:

  • 客户端如何发现 Leader
  • Raft 如何支持线性化语义

这些问题对于所有基于一致性的系统都存在,并且 Raft 的解决方案和其他的也差不多。

  • 客户端发送所有请求都要给 Leader
    第一次通信会随机联系一个节点,如果不是 Leader ,会被拒绝并提供最近接收的 Leader 信息(AE RPC 包含 Leader 地址),即重定向
    如果 Leader 宕机,请求超时,客户重试即可。

  • Raft 的目标是要实现线性化语义(每次操作立即执行,在调用和收到回复之间只执行一次)
    若 Leader 提交了客户端的操作日志,在回复客户端之前宕机,客户端重试。此时该指令可能执行两次。
    解决方案是客户端对每条指令赋予唯一序列号,状态机接受的序列号被执行的指令直接返回结果

  • 只读操作可以不需要记录日志,但是旧 Leader 响应客户端时可能已经卸任,此时返回的是脏数据。需要两个额外机制保证不返回脏数据

  1. Leader 必须有关于被提交日志的最新信息,刚上任时可能不知道哪些已提交,所以需要**提交一个 no-op(空命令)**日志条目。(这里在 5.4 安全性部分提过一次,保证选举限制成立)
  2. Leader 在响应选举请求前,检查自己是否已被卸任。只需要和集群中大多数节点交换一次心跳信息即可。

可选项:Leader 可以通过心跳机制实现租约机制,但是这种方法依赖时间来保证安全性(假设时间误差是有界的)。

参考下 GFS 中的租约机制


后面的部分不是论文重点了,不详细写了。

9. 算法实现和评估

从三个方面来评估 Raft 算法:

  • 可理解性:比 Paxos 好多了

  • 正确性:前面已经证明过了

  • 性能:和 Paxos 差不多
    选举超时时间随机化能大大加快领导人选举过程收敛。
    通过减少选举超时时间可以减少系统的宕机时间,但是继续减小会违反 Raft 的时间不等式需求。建议使用 150-300 毫秒

10. 相关工作

介绍了一些其他一致性算法的工作。

Raft 和 Paxos 最大的不同之处就在于 Raft 的强 Leader 特性。

Raft 比 VR 和 ZooKeeper 拥有更少的机制因为 Raft 尽可能的减少了非领导人的功能。

Raft 的强领导人模型简化了整个算法,但是同时也排斥了一些性能优化的方法。例如,平等主义 Paxos (EPaxos)在某些没有领导人的情况下可以达到很高的性能。

  1. 结论

  2. 感谢

  3. 参考


image.png

感谢大家的观看!!!创作不易,如果觉得我写的好的话麻烦点点赞👍支持一下,谢谢!!!