Files
6.824-golabs-2021-6.824/docs/papers/raft-extended-cn.md
2026-02-25 23:13:59 +08:00

666 lines
71 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 寻找一种可理解的共识算法(扩展版)
**Diego Ongaro 与 John Ousterhout**
斯坦福大学
*本技术报告为 [32] 的扩展版;页边灰色条标注为新增内容。发表于 2014 年 5 月 20 日。*
## 摘要
Raft 是一种用于管理复制日志的共识算法。其效果与Paxos 等价,效率相当,但结构不同;这使得 Raft 比 Paxos 更易理解并为构建实用系统提供了更好的基础。为提高可理解性Raft 将共识的关键要素分离,如 leader 选举、日志复制与安全性,并强化一致性以减少需要考虑的状态数量。用户研究表明,学生更容易学会 Raft 而非 Paxos。Raft 还包含一种新的集群成员变更机制,通过重叠多数来保证安全。
## 1 引言
共识算法使一组机器能够作为可承受部分成员故障的协调整体工作因此在构建可靠的大规模软件系统中扮演关键角色。过去十年间Paxos [15, 16] 主导了共识算法的讨论多数共识实现基于或受其影响Paxos 也成为教授共识的主要载体。
遗憾的是尽管有大量使其更易理解的尝试Paxos 仍然非常难懂。此外,其架构需要复杂改动才能支持实用系统。因此,无论是系统构建者还是学生都在与 Paxos 角力。
在自身与 Paxos 角力之后,我们着手寻找一种新的共识算法,为系统构建和教育提供更好基础。我们的做法不同寻常:首要目标是可理解性——能否为实用系统定义一种共识算法,并以明显比 Paxos 更易学的方式描述?此外,我们希望算法能帮助形成系统构建者所必需的直觉。算法不仅要正确,而且要让人一眼看出为何正确。
这项工作的结果是一种名为 Raft 的共识算法。在设计 Raft 时我们采用了提高可理解性的具体技术包括分解Raft 将 leader 选举、日志复制与安全性分开)和状态空间缩减(相对 PaxosRaft 减少了非确定性程度以及各服务器日志不一致的方式)。对两所大学 43 名学生的用户研究表明Raft 明显更易理解:在学完两种算法后,其中 33 名学生在回答 Raft 相关问题上优于 Paxos。
Raft 与现有共识算法(尤其是 Oki 与 Liskov 的 Viewstamped Replication [29, 22])在许多方面相似,但具有若干新特点:
- **强 leader** Raft 采用比其他共识算法更强的 leader 形式。例如,日志条目仅从 leader 流向其他服务器,简化了复制日志的管理并使 Raft 更易理解。
- **Leader 选举:** Raft 使用随机化定时器选举 leader在已有心跳机制上只增加少量机制即可简单快速地解决冲突。
- **成员变更:** Raft 的集群服务器集变更机制采用新的联合共识joint consensus方式在过渡期间两种配置的多数派重叠使集群在配置变更期间仍可正常运作。
我们相信 Raft 在教育与实现基础上均优于 Paxos 及其他共识算法:更简单、更易理解;描述足够完整以满足实用系统需求;有多个开源实现并被多家公司使用;安全性已形式化规范并证明;效率与其他算法相当。
本文其余部分介绍复制状态机问题(第 2 节)、讨论 Paxos 的优缺点(第 3 节)、描述我们关于可理解性的一般思路(第 4 节)、给出 Raft 共识算法(第 58 节)、评估 Raft第 9 节)并讨论相关工作(第 10 节)。
## 2 复制状态机
共识算法通常出现在复制状态机 [37] 的语境中。在该方法中,各服务器上的状态机计算相同状态的相同副本,即使部分服务器宕机也能继续运行。复制状态机用于解决分布式系统中的多种容错问题。例如,具有单一集群 leader 的大规模系统(如 GFS [8]、HDFS [38]、RAMCloud [33])通常使用独立的复制状态机来管理 leader 选举并存储必须在 leader 崩溃后保留的配置信息。复制状态机的例子包括 Chubby [2] 和 ZooKeeper [11]。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772018749187-e864c43b41d75fc57427ff64c909fded.png)
**图 1** 复制状态机架构。共识算法管理一个包含来自客户端的 state machine 命令的复制日志。各 state machine 按相同顺序处理日志中的命令,因此产生相同输出。
复制状态机通常用复制日志实现,如图 1。每台服务器保存一个包含一系列命令的日志由其 state machine 按序执行。每条日志包含相同命令且顺序相同,故每台 state machine 处理相同命令序列。由于 state machine 是确定性的,每台计算出相同状态与相同输出序列。
保持复制日志一致是共识算法的职责。服务器上的共识模块接收客户端命令并加入其日志,与其他服务器上的共识模块通信,确保即使部分服务器故障,每条日志最终也以相同顺序包含相同请求。命令被正确复制后,各服务器的 state machine 按日志顺序处理它们,并将输出返回客户端。因此,这些服务器在行为上如同一台高可用的 state machine。
实用系统中的共识算法通常具有以下性质:
- 在所有非拜占庭条件下(包括网络延迟、分区、丢包、重复与乱序)保证安全(永不返回错误结果)。
- 只要任意多数服务器可用且能彼此及与客户端通信,系统就完全可用。因此,典型的五台服务器集群可容忍任意两台故障。假定服务器以停机方式故障;之后可从稳定存储恢复并重新加入集群。
- 不依赖时间假设来保证日志一致性:故障时钟与极端消息延迟最多导致可用性问题。
- 在常见情况下只要集群多数在一次远程过程调用RPC中响应命令即可完成少数慢速服务器不必影响整体性能。
## 3 Paxos 的问题
过去十年Leslie Lamport 的 Paxos 协议 [15] 几乎成为共识的同义词它是课程中最常讲授的协议多数共识实现以其为起点。Paxos 首先定义能在单一决策(如一条复制日志条目)上达成一致的协议,我们称这部分为 single-decree Paxos。Paxos 随后将多个该协议实例组合以支持一系列决策即一条日志multi-Paxos。Paxos 保证安全与活性,并支持集群成员变更。其正确性已获证明,在正常情况下也高效。
但 Paxos 有两个明显缺点。
第一Paxos 极其难懂。完整阐述 [15] 以晦涩著称;很少有人能理解,且需付出很大努力。因此出现了多种用更简单语言解释 Paxos 的尝试 [16, 20, 21]。这些解释聚焦 single-decree 子集,仍然很有挑战。在 NSDI 2012 的非正式调查中,即便在资深研究者中也很少有人对 Paxos 感到自如。我们自己也与 Paxos 角力;直到阅读多种简化解释并设计了自己的替代协议,才理解完整协议,这一过程花了近一年。
我们假设 Paxos 的晦涩源于它以 single-decree 子集为基础。Single-decree Paxos 稠密而微妙:分为两个阶段,既没有简单的直观解释,也无法独立理解。因此难以形成“为何 single-decree 协议有效”的直觉。multi-Paxos 的组合规则又带来大量复杂性与微妙之处。我们相信,在多个决策上达成共识(即一条日志而非单条)的整体问题,可以用更直接、更显然的方式分解。
第二Paxos 并未为构建实用实现提供良好基础。原因之一是没有被广泛认同的 multi-Paxos 算法。Lamport 的叙述主要关于 single-decree Paxos他勾勒了 multi-Paxos 的可能做法,但许多细节缺失。虽有 [26]、[39]、[13] 等对 Paxos 的充实与优化尝试,它们彼此不同,也与 Lamport 的草图不同。Chubby [4] 等系统实现了类 Paxos 算法,但多数细节未公开。
此外Paxos 的架构不利于构建实用系统;这也是 single-decree 分解的后果。例如,先独立选出一组日志条目再合并成顺序日志收益甚少,只会增加复杂度。围绕日志设计系统更简单高效:新条目在约束顺序下顺序追加。另一问题是 Paxos 核心采用对称的对等方式(尽管后来建议弱形式的 leader 作为性能优化)。在只做一次决策的简化世界里合理,但实用系统很少如此。若要做出系列决策,先选 leader、再由 leader 协调决策更简单、更快。
因此,实用系统与 Paxos 相去甚远。每个实现从 Paxos 起步,发现实现困难,然后发展出差异很大的架构。这既耗时又易错,而理解 Paxos 的困难又加剧了问题。Paxos 的表述或许适合证明其正确性定理,但实际实现与 Paxos 差异如此之大以至于这些证明价值有限。Chubby 实现者的以下评论很有代表性:
> Paxos 算法描述与真实系统需求之间存在显著鸿沟……最终系统将基于未经证明的协议 [4]。
>
鉴于这些问题我们得出结论Paxos 无论对系统构建还是教育都不是良好基础。考虑到共识在大规模软件系统中的重要性,我们决定尝试设计一种比 Paxos 性质更好的替代共识算法。Raft 就是该实验的产物。
## 4 为可理解性而设计
设计 Raft 时我们有若干目标:必须为系统构建提供完整且实用的基础,显著减少开发者所需的设计工作;必须在所有条件下安全、在典型运行条件下可用;必须对常见操作高效。但我们最重要也最困难的目标是可理解性。必须让大量读者能轻松理解算法;此外,必须能形成对算法的直觉,以便系统构建者能做出实际实现中不可避免的扩展。
在 Raft 设计中有许多需要在不同方案间做选择的点。在这些情况下我们按可理解性评估:每种方案有多难解释(例如状态空间多复杂、是否有微妙含义)?读者能否完全理解该方案及其含义?
我们承认这类分析主观性很强;尽管如此,我们采用了两条通用技术。其一是众所周知的问题分解:尽可能将问题拆成可相对独立地解决、解释和理解的子问题。例如在 Raft 中我们分离了 leader 选举、日志复制、安全性与成员变更。
其二是通过减少需要考虑的状态来简化状态空间,使系统更一致,并在可能时消除非确定性。具体地,日志不允许出现空洞,且 Raft 限制了日志彼此不一致的方式。尽管在多数情况下我们试图消除非确定性,但在某些情形下非确定性反而提高可理解性。尤其是随机化方法会引入非确定性,却倾向于用统一方式处理所有可能选择(“任选其一;无所谓”)来缩小状态空间。我们用随机化简化了 Raft 的 leader 选举算法。
## 5 Raft 共识算法
Raft 是第 2 节所述形式的复制日志管理算法。图 2 以浓缩形式概括该算法供参考,图 3 列出算法的关键性质;这些图的内容将在本节其余部分分段讨论。
Raft 通过先选举一个 distinguished leader再赋予该 leader 管理复制日志的完整责任来实现共识。Leader 接受来自客户端的日志条目、在其他服务器上复制它们,并告知各服务器何时可以安全地将日志条目应用到其 state machine。拥有 leader 简化了复制日志的管理:例如 leader 可在不咨询其他服务器的情况下决定将新条目放在日志何处,数据以简单方式从 leader 流向其他服务器。Leader 可能故障或与其他服务器断开,此时会选举新 leader。
在 leader 方案下Raft 将共识问题分解为三个相对独立的子问题,分别在以下小节讨论:
- **Leader 选举:** 当现有 leader 失败时必须选出新 leader第 5.2 节)。
- **日志复制:** Leader 必须接受客户端的日志条目并在集群中复制,使其他日志与其一致(第 5.3 节)。
- **安全性:** Raft 的关键安全性质是图 3 中的 State Machine Safety若某服务器已将某条日志条目应用到其 state machine则其他服务器不得对同一 index 应用不同的日志条目。第 5.4 节描述 Raft 如何保证该性质;解决方案涉及对第 5.2 节选举机制的额外限制。
在介绍共识算法之后,本节还会讨论可用性以及时间在系统中的作用。
> **State**
>
>
> **所有服务器上的持久状态**
>
> *(在响应 RPC 前更新到稳定存储)*
>
> `currentTerm`: 服务器已知的最新 term首次启动时初始化为 0单调递增
>
> `votedFor`: 当前 term 获得投票的 candidateId若无则为 null
>
> `log[]`: 日志条目;每条包含发给 state machine 的 command以及 leader 收到该条目时的 term首条 index 为 1
>
>
> **所有服务器上的易失状态:**
>
> `commitIndex`: 已知已提交的最高日志条目的 index初始化为 0单调递增
>
> `lastApplied`: 已应用到 state machine 的最高日志条目的 index初始化为 0单调递增
>
>
> **Leader 上的易失状态:**
>
> *(选举后重新初始化)*
>
>
> `nextIndex[]`: 对每个服务器,要发给该服务器的下一条日志条目的 index初始化为 leader 最后一条日志 index + 1
>
> `matchIndex[]`: 对每个服务器,已知已在该服务器上复制的最高日志条目的 index初始化为 0单调递增
>
> **AppendEntries RPC**
>
>
> *由 leader 调用以复制日志条目§5.3也用作心跳§5.2)。*
>
>
> **参数:**
>
> `term`: leader 的 term
>
> `leaderId`: 便于 follower 将客户端重定向到 leader
>
> `prevLogIndex`: 紧接在新条目之前的日志条目的 index
>
> `prevLogTerm`: prevLogIndex 条目的 term
>
> `entries[]`: 要存储的日志条目(心跳时为空;可为效率一次发送多条)
>
> `leaderCommit`: leader 的 commitIndex
>
>
> **返回:**
>
> `term`: currentTerm供 leader 更新自身
>
> `success`: 若 follower 在 prevLogIndex 处包含与 prevLogTerm 匹配的条目则为 true
>
>
> **接收者实现:**
>
> 1. 若 term < currentTerm 则返回 false§5.1
>
> 2. 若日志在 prevLogIndex 处不包含 term 与 prevLogTerm 匹配的条目则返回 false§5.3
>
> 3. 若已有条目与新条目冲突(同 index 不同 term删除该条目及之后所有条目§5.3
>
> 4. 追加尚未在日志中的新条目
>
> 5. 若 leaderCommit > commitIndex则令 commitIndex = min(leaderCommit, 最后一条新条目的 index)
>
> **RequestVote RPC**
>
>
> *由 candidate 调用以收集选票§5.2)。*
>
>
> **参数:**
>
> `term`: candidate 的 term
>
> `candidateId`: 请求投票的 candidate
>
> `lastLogIndex`: candidate 最后一条日志条目的 index§5.4
>
> `lastLogTerm`: candidate 最后一条日志条目的 term§5.4
>
>
> **返回:**
>
> `term`: currentTerm供 candidate 更新自身
>
> `voteGranted`: true 表示 candidate 获得投票
>
>
> **接收者实现:**
>
> 1. 若 term < currentTerm 则返回 false§5.1
>
> 2. 若 votedFor 为 null 或 candidateId且 candidate 的日志至少与接收者一样新则授予投票§5.2, §5.4
>
> **服务器规则**
>
>
> **所有服务器:**
>
> - 若 commitIndex > lastApplied递增 lastApplied将 log[lastApplied] 应用到 state machine§5.3
>
> - 若 RPC 请求或响应包含 term T > currentTerm令 currentTerm = T转为 follower§5.1
>
>
> **Follower§5.2**
>
> - 响应来自 candidate 与 leader 的 RPC
>
> - 若在未收到当前 leader 的 AppendEntries RPC 且未向 candidate 投票的情况下选举超时:转为 candidate
>
>
> **Candidate§5.2**
>
> - 转为 candidate 时发起选举:
>
> - 递增 currentTerm
>
> - 投票给自己
>
> - 重置选举定时器
>
> - 向所有其他服务器发送 RequestVote RPC
>
> - 若收到多数服务器的投票:成为 leader
>
> - 若收到新 leader 的 AppendEntries RPC转为 follower
>
> - 若选举超时:发起新一轮选举
>
>
> **Leader**
>
> - 当选后:向每台服务器发送初始空 AppendEntries RPC心跳在空闲期重复以阻止选举超时§5.2
>
> - 若从客户端收到 command追加条目到本地日志在条目应用到 state machine 后响应§5.3
>
> - 若某 follower 的 last log index ≥ nextIndex从 nextIndex 开始发送包含日志条目的 AppendEntries RPC
>
> - 若成功:更新该 follower 的 nextIndex 与 matchIndex§5.3
>
> - 若因日志不一致导致 AppendEntries 失败:递减 nextIndex 并重试§5.3
>
> - 若存在 N 使得 N > commitIndex、多数 matchIndex[i] ≥ N 且 log[N].term == currentTerm令 commitIndex = N§5.3, §5.4
>
**图 2** Raft 共识算法浓缩摘要不含成员变更与日志压缩。左上框中的服务器行为被描述为一系列独立、重复触发的规则。§5.2 等节号表示该特性在何处讨论。形式化规范 [31] 更精确地描述了算法。
> **Election Safety** 在给定 term 中至多选出一名 leader。§5.2
>
> **Leader Append-Only** leader 从不覆盖或删除其日志中的条目只追加新条目。§5.3
>
> **Log Matching** 若两条日志在相同 index 和 term 处包含条目,则在该 index 之前两条日志完全相同。§5.3
>
> **Leader Completeness** 若某日志条目在给定 term 内被提交,则该条目将出现在所有更大 term 的 leader 的日志中。§5.4
>
> **State Machine Safety** 若某服务器已将某 index 处的日志条目应用到其 state machine则其他服务器永远不会对同一 index 应用不同的日志条目。§5.4.3
>
**图 3** Raft 保证这些性质始终成立。节号表示各性质在何处讨论。
### 5.1 Raft 基础
一个 Raft 集群包含若干服务器典型数量为五台可容忍两台故障。任意时刻每台服务器处于三种状态之一leader、follower 或 candidate。正常运行时恰好有一名 leader其余均为 follower。Follower 是被动的:不主动发请求,只响应 leader 与 candidate 的请求。Leader 处理所有客户端请求(若客户端联系 followerfollower 会将其重定向到 leader。第三种状态 candidate 用于选举新 leader见第 5.2 节。图 4 展示了状态及其转换。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021655825-b3ea45058d49e803bfb33affef807129.png)
**图 4** 服务器状态。Follower 仅响应其他服务器的请求。若 follower 未收到任何通信,则变为 candidate 并发起选举。获得完整集群多数投票的 candidate 成为新 leader。Leader 通常运行直至故障。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021667371-eaecd78542c7868e8b53f9072f9251c6.png)
**图 5** 时间被划分为 term每个 term 以选举开始。选举成功后,一名 leader 管理集群直至该 term 结束。部分选举会失败,此时 term 在没有选出 leader 的情况下结束。不同服务器可能在不同时刻观察到 term 之间的转换。
Raft 将时间划分为任意长度的 term如图 5。Term 用连续整数编号。每个 term 以选举开始,一名或多名 candidate 按第 5.2 节尝试成为 leader。若某 candidate 赢得选举,则在该 term 剩余时间担任 leader。有时选举会导致选票分散。此时 term 在没有 leader 的情况下结束;新的 term伴随新选举很快开始。Raft 保证在给定 term 中至多有一名 leader。
不同服务器可能在不同时刻观察到 term 转换,某些情况下某服务器可能观察不到某次选举甚至整个 term。Term 在 Raft 中充当逻辑时钟 [14],使服务器能检测过时信息(如过期的 leader。每台服务器保存当前 term 编号,随时间单调递增。服务器通信时会交换当前 term若一方的 currentTerm 小于另一方,则更新为较大值。若 candidate 或 leader 发现自己的 term 已过时,立即恢复为 follower。若服务器收到带过期 term 的请求,则拒绝该请求。
Raft 服务器通过远程过程调用RPC通信基本共识算法只需两类 RPC。RequestVote RPC 由 candidate 在选举时发起(第 5.2 节AppendEntries RPC 由 leader 发起以复制日志条目并作为心跳(第 5.3 节)。第 7 节增加第三种 RPC 用于在服务器间传输快照。若未及时收到响应,服务器会重试 RPC并并行发起 RPC 以获得最佳性能。
### 5.2 Leader 选举
Raft 用心跳机制触发 leader 选举。服务器启动时以 follower 身份开始。只要持续收到来自 leader 或 candidate 的有效 RPC就保持 follower。Leader 向所有 follower 定期发送心跳(不携带日志条目的 AppendEntries RPC以维持权威。若 follower 在一段称为选举超时election timeout的时间内未收到任何通信则假定没有可用 leader 并开始选举以选出新 leader。
为开始选举follower 递增其 currentTerm 并转为 candidate。随后给自己投票并并行向集群中其他每台服务器发送 RequestVote RPC。Candidate 保持该状态直到发生以下三种情况之一:(a) 赢得选举,(b) 其他服务器确立为 leader或 (c) 一段时间内没有胜者。
若某 candidate 在同一 term 内获得完整集群中多数服务器的投票,则赢得选举。每台服务器在给定 term 内至多投给一名 candidate先到先得注意第 5.4 节对投票有额外限制)。多数规则保证在特定 term 中至多一名 candidate 能赢得选举(图 3 的 Election Safety。Candidate 赢得选举后成为 leader随后向所有其他服务器发送心跳以确立权威并阻止新选举。
在等待选票时candidate 可能收到自称 leader 的服务器发来的 AppendEntries RPC。若该 leader 的 term包含在其 RPC 中)至少与 candidate 的 currentTerm 一样大candidate 承认该 leader 合法并恢复为 follower。若 RPC 中的 term 小于 candidate 的 currentTermcandidate 拒绝该 RPC 并继续作为 candidate。
第三种可能是 candidate 既未赢也未输:若多台 follower 同时变为 candidate选票可能分散以致无人获得多数。此时每名 candidate 会超时,通过递增 term 并发起新一轮 RequestVote RPC 开始新选举。但若没有额外措施,分散选票可能无限重复。
Raft 使用随机化选举超时来确保分散选票罕见且能快速解决。为防止分散,选举超时从固定区间(如 150300ms中随机选择。这样在多数情况下只有单台服务器会超时它在其他服务器超时前赢得选举并发出心跳。同一机制也用于处理已发生的分散每名 candidate 在选举开始时重启其随机选举超时,并等待超时后再开始下一轮选举,从而降低新一轮再次分散的概率。第 9.3 节表明该方式能快速选出 leader。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021707746-7e0a3337f8425999c4c123b9b1b2e177.png)
**图 6** 日志由按序编号的条目组成。每条包含创建时的 term框中数字以及发给 state machine 的 command。当某条目可以安全地应用到 state machine 时该条目被视为已提交committed
选举是可理解性如何指导我们在设计备选间做选择的例子。我们最初计划使用排名:每名 candidate 被赋予唯一 rank用于在竞争 candidate 间选择。若某 candidate 发现另一名 rank 更高的 candidate会恢复为 follower 以便高 rank 的 candidate 更容易赢得下一轮选举。我们发现该方式在可用性上带来微妙问题(若高 rank 服务器故障,低 rank 服务器可能需要超时并再次成为 candidate但若过早这样做会重置选举进度。我们对算法做了多次调整但每次调整后都会出现新的边界情况。最终我们得出结论随机重试方式更直观、更易理解。
### 5.3 日志复制
Leader 选出后开始处理客户端请求。每个客户端请求包含要由复制 state machine 执行的 command。Leader 将 command 作为新条目追加到其日志,然后并行向其他每台服务器发送 AppendEntries RPC 以复制该条目。当条目被安全复制后见下文leader 将条目应用到其 state machine 并把执行结果返回客户端。若 follower 崩溃、运行缓慢或网络丢包leader 会无限重试 AppendEntries RPC即便在已响应客户端之后直到所有 follower 最终都存储了所有日志条目。
日志组织如图 6。每条日志条目存储一条 state machine command 以及 leader 收到该条目时的 term。日志条目中的 term 用于检测日志间的不一致并保证图 3 中的部分性质。每条日志条目还有一个整数 index 标识其在日志中的位置。
Leader 决定何时可以安全地将日志条目应用到 state machine这样的条目称为已提交committed。Raft 保证已提交条目持久化,并最终被所有可用 state machine 执行。当创建该条目的 leader 已将其复制到多数服务器时(如图 6 中的条目 7该日志条目即被提交。这也会提交 leader 日志中该条目之前的所有条目,包括之前 leader 创建的条目。第 5.4 节讨论 leader 更替后应用该规则的一些细节并说明该提交定义是安全的。Leader 记录已知已提交的最高 index并在后续 AppendEntries RPC含心跳中携带该 index使其他服务器最终得知。Follower 一旦得知某日志条目已提交,就按日志顺序将其应用到本地 state machine。
我们将 Raft 的日志机制设计为在不同服务器的日志间保持高度一致。这不仅简化系统行为、使其更可预测也是保证安全的重要一环。Raft 维持以下性质,它们共同构成图 3 的 Log Matching Property
- 若两条不同日志中的条目具有相同 index 和 term则它们存储相同 command。
- 若两条不同日志中的条目具有相同 index 和 term则在该 index 之前两条日志完全相同。
第一条来自leader 在给定 term 内对给定 log index 至多创建一条条目,且日志条目在日志中的位置从不改变。第二条由 AppendEntries 的简单一致性检查保证。发送 AppendEntries RPC 时leader 会带上其日志中紧接在新条目之前那条的 index 和 term。若 follower 在其日志中找不到 index 与 term 都匹配的条目,则拒绝新条目。该一致性检查充当归纳步:日志的初始空状态满足 Log Matching Property且只要扩展日志一致性检查就保持该性质。因此每当 AppendEntries 成功返回leader 就知道 follower 的日志在与新条目一致的部分与其相同。
正常运行时leader 与 follower 的日志保持一致,故 AppendEntries 的一致性检查不会失败。但 leader 崩溃可能使日志不一致(旧 leader 可能尚未将其日志中所有条目完全复制)。这些不一致可能在一系列 leader 与 follower 崩溃中叠加。图 7 展示了 follower 日志与新 leader 可能存在的差异follower 可能缺少 leader 上存在的条目,可能有多出 leader 上没有的条目,或两者皆有。日志中缺失与多余条目可能跨越多个 term。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021726711-ccec4d9e4a5aee87232a69efab58beaa.png)
**图 7** 当顶部 leader 上台时follower 日志可能出现 (a)(f) 任一情况。每格代表一条日志条目;格内数字为其 term。Follower 可能缺少条目 (ab)、可能有额外未提交条目 (cd),或兼有 (ef)。
在 Raft 中leader 通过强制 follower 的日志与自己的副本一致来处理不一致。即 follower 日志中的冲突条目会被 leader 日志中的条目覆盖。第 5.4 节将说明在加上一条限制后这是安全的。为使某 follower 的日志与己一致leader 必须找到两条日志一致的最新日志条目,删除该点之后 follower 日志中的条目,并向该 follower 发送该点之后 leader 的所有条目。这些操作都在 AppendEntries RPC 执行一致性检查时完成。Leader 为每个 follower 维护 nextIndex即将要发给该 follower 的下一条日志条目的 index。Leader 刚上台时,将所有 nextIndex 初始化为其日志最后一条的 index 加 1图 7 中为 11。若某 follower 的日志与 leader 不一致,下一次 AppendEntries RPC 中的一致性检查会失败。被拒后 leader 递减 nextIndex 并重试 AppendEntries RPC。最终 nextIndex 会到达 leader 与 follower 日志一致的位置。此时 AppendEntries 成功,会删除 follower 日志中的冲突条目并追加 leader 的条目(若有)。一旦 AppendEntries 成功,该 follower 的日志在本 term 内将与 leader 一致并保持。
若需要,可优化协议以减少被拒的 AppendEntries RPC 数量。例如,拒绝 AppendEntries 时 follower 可附带冲突条目的 term 以及其在该 term 下存储的首条 index。据此 leader 可一次将 nextIndex 递减以跳过该 term 内所有冲突条目;每个有冲突的 term 只需一次 AppendEntries RPC而非每条条目一次。实践中我们怀疑该优化必要性不高因为故障不常发生且不一致条目通常不会很多。
在此机制下leader 上台时无需特别操作即可恢复日志一致性。只需按正常流程运行,日志会在 AppendEntries 一致性检查失败时自动收敛。Leader 从不覆盖或删除其自身日志中的条目(图 3 的 Leader Append-Only Property
该日志复制机制具备第 2 节所述共识性质只要多数服务器在线Raft 就能接受、复制并应用新日志条目;正常情况下新条目只需一轮发往集群多数的 RPC 即可复制;单台慢 follower 不会影响性能。
### 5.4 安全性
前几节描述了 Raft 如何选举 leader 与复制日志条目。但仅这些机制尚不足以保证每台 state machine 以相同顺序执行完全相同的一组 command。例如某 follower 可能在 leader 提交若干日志条目时不可用,随后被选为 leader 并用新条目覆盖这些条目,导致不同 state machine 执行不同命令序列。
本节通过增加“哪些服务器可被选为 leader”的限制来补全 Raft 算法。该限制保证任意 term 的 leader 都包含之前 term 已提交的所有条目(图 3 的 Leader Completeness Property。在此基础上我们再精确化提交规则。最后给出 Leader Completeness 的证明梗概,并说明其如何导致复制 state machine 的正确行为。
#### 5.4.1 选举限制
在任何基于 leader 的共识算法中leader 最终必须存有所有已提交的日志条目。在 Viewstamped Replication [22] 等算法中leader 即使最初不包含所有已提交条目也可当选。这些算法包含额外机制,在选举过程中或之后识别并传输缺失条目给新 leader。遗憾的是这会带来大量额外机制与复杂度。Raft 采用更简单的方式:保证每个新 leader 从当选那一刻起就拥有之前 term 的所有已提交条目,无需再传输。这意味着日志条目只从 leader 流向 follower且 leader 从不覆盖其日志中已有条目。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021748628-83a9e0f75ea266cc6c23d1e7d2b3ac27.png)
**图 8** 时间序列说明 leader 无法仅凭旧 term 的日志条目确定提交。在 (a) 中 S1 为 leader 并部分复制了 index 2 处的日志条目。在 (b) 中 S1 崩溃S5 在 S3、S4 与自己的投票下当选 term 3 的 leader并在 log index 2 接受了一条不同条目。在 (c) 中 S5 崩溃S1 重启、当选 leader 并继续复制。此时 term 2 的日志条目已在多数服务器上复制,但尚未提交。若 S1 如 (d) 般崩溃S5 可能当选 leader获 S2、S3、S4 投票)并用其 term 3 的条目覆盖。但若 S1 在崩溃前如 (e) 那样将当前 term 的一条条目复制到多数服务器则该条目被提交S5 无法赢得选举)。此时日志中该条目之前的所有条目也被提交。
Raft 通过投票过程阻止其日志不包含所有已提交条目的 candidate 赢得选举。Candidate 必须联系集群多数才能当选,即每个已提交条目都至少出现在这些服务器之一上。若 candidate 的日志至少与该多数中任一日志一样新“一样新”的定义见下则其将拥有所有已提交条目。RequestVote RPC 实现该限制RPC 携带 candidate 的日志信息,若投票者自己的日志比 candidate 更新,则拒绝投票。
Raft 通过比较两条日志最后一条的 index 与 term 来判断谁更新。若最后一条的 term 不同term 更大的日志更新。若最后一条 term 相同,则更长的日志更新。
#### 5.4.2 提交之前 term 的条目
如第 5.3 节所述当某条目已存储在多数服务器上时leader 即知该 term 的该条目已提交。若 leader 在提交前崩溃,后续 leader 会尝试完成复制。但 leader 不能仅因某条旧 term 的条目已在多数服务器上就立即认定其已提交。图 8 展示了旧日志条目已在多数服务器上仍可能被后续 leader 覆盖的情况。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021818108-18ddd74b8e1b54a1df11856ad7606e86.png)
**图 9** 若 S1term T 的 leader提交了其 term 的一条新日志条目,而 S5 当选后续 term U 的 leader则至少有一台服务器S3既接受了该日志条目又投票给 S5。
为避免图 8 这类问题Raft 从不通过统计副本数来提交之前 term 的日志条目。只有 leader 当前 term 的日志条目才通过统计副本提交;一旦当前 term 的某条以这种方式被提交,则凭借 Log Matching Property其之前所有条目被间接提交。某些情况下 leader 可以安全地推断某条更早的日志条目已提交(例如该条目已存在于每台服务器),但 Raft 为简单起见采用更保守的做法。
Raft 在提交规则上承担这份额外复杂度,是因为 leader 在复制之前 term 的条目时保留其原始 term。在其他共识算法中若新 leader 重新复制之前“term”的条目必须用新的“term 编号”复制。Raft 的做法使推理日志条目更简单,因为它们在不同时间和不同日志中保持相同 term。此外Raft 的新 leader 需要发送的来自之前 term 的日志条目少于其他算法(其他算法必须先发送冗余日志条目并重新编号才能提交)。
#### 5.4.3 安全性论证
在完整 Raft 算法下,我们可以更精确地论证 Leader Completeness Property 成立(该论证基于安全性证明,见第 9.2 节)。假设 Leader Completeness 不成立,然后推出矛盾。设 term T 的 leaderleaderT提交了其 term 的一条日志条目,但该条目未被某未来 term 的 leader 存储。考虑最小的 U > T 使得其 leaderleaderU不存储该条目。
1. 该已提交条目在 leaderU 当选时必然不在其日志中leader 从不删除或覆盖条目)。
2. leaderT 将该条目复制到集群多数leaderU 也获得了集群多数的投票。因此至少有一台服务器(“投票者”)既接受了 leaderT 的该条目又投票给 leaderU如图 9。该投票者是得到矛盾的关键。
3. 投票者必然在投票给 leaderU 之前已接受 leaderT 的该已提交条目;否则会拒绝 leaderT 的 AppendEntries 请求(其 currentTerm 会高于 T
4. 投票者在投票给 leaderU 时仍保存该条目,因为(由假设)每个中间的 leader 都包含该条目leader 从不删除条目follower 仅在与 leader 冲突时才删除条目。
5. 投票者将票投给 leaderU故 leaderU 的日志至少与投票者一样新。由此得到两种矛盾之一。
6. 若投票者与 leaderU 的最后一条日志 term 相同,则 leaderU 的日志至少与投票者一样长,故其日志包含投票者日志中的每条条目。这与“投票者包含已提交条目而 leaderU 假定不包含”矛盾。
7. 否则 leaderU 的最后一条日志 term 必然大于投票者的。且该 term 大于 T因为投票者的最后一条日志 term 至少为 T其包含 term T 的已提交条目)。创建 leaderU 最后一条日志条目的更早 leader 由假设必然在其日志中包含该已提交条目。于是由 Log Matching PropertyleaderU 的日志也必然包含该已提交条目,矛盾。
8. 矛盾完成。故所有大于 T 的 term 的 leader 都包含在 term T 内提交的 term T 的所有条目。
9. Log Matching Property 保证后续 leader 也会包含被间接提交的条目,如图 8(d) 中的 index 2。
由 Leader Completeness Property 可证明图 3 的 State Machine Safety Property若某服务器已将某 index 处的日志条目应用到其 state machine则其他服务器永远不会对同一 index 应用不同的日志条目。当某服务器将某日志条目应用到其 state machine 时,其日志在该条目之前必须与 leader 的日志一致,且该条目必须已提交。考虑任意服务器对某 log index 进行应用的 term 中最小的那个Log Completeness Property 保证所有更大 term 的 leader 都会存储该同一条目,故在更大 term 中应用该 index 的服务器会应用相同值。因此 State Machine Safety 成立。
最后Raft 要求服务器按 log index 顺序应用条目。结合 State Machine Safety即所有服务器将以相同顺序向 state machine 应用完全相同的日志条目集合。
### 5.5 Follower 与 candidate 崩溃
此前我们主要关注 leader 故障。Follower 与 candidate 崩溃比 leader 崩溃简单得多,且处理方式相同。若 follower 或 candidate 崩溃,发往它的 RequestVote 与 AppendEntries RPC 将失败。Raft 通过无限重试处理这些故障若崩溃的服务器重启RPC 会成功完成。若某服务器在完成 RPC 但尚未响应时崩溃,重启后会再次收到同一 RPC。Raft 的 RPC 是幂等的,故不会造成问题。例如,若 follower 收到的 AppendEntries 请求中的日志条目已在其日志中,它会忽略新请求中的这些条目。
### 5.6 时间与可用性
我们对 Raft 的要求之一是安全不依赖时间系统不能仅因某事件比预期更快或更慢就产生错误结果。但可用性系统及时响应客户端的能力必然依赖时间。例如若消息往返时间比服务器典型故障间隔还长candidate 将无法在选举中坚持足够久;没有稳定的 leaderRaft 无法取得进展。
Leader 选举是 Raft 中最依赖时间的部分。只要系统满足以下时间不等式Raft 就能选出并维持稳定 leader
**broadcastTime ≪ electionTimeout ≪ MTBF**
其中 broadcastTime 是服务器并行向集群中每台服务器发送 RPC 并收到响应的平均时间electionTimeout 即第 5.2 节的选举超时MTBF 是单台服务器的平均故障间隔。广播时间应比选举超时小一个数量级,以便 leader 可靠地发送阻止 follower 发起选举所需的心跳;结合选举超时的随机化,该不等式也使选票分散不太可能发生。选举超时应比 MTBF 小几个数量级使系统稳定进展。Leader 崩溃时,系统大约在选举超时时间内不可用;我们希望这只占整体时间的一小部分。
广播时间与 MTBF 由底层系统决定选举超时则需我们选择。Raft 的 RPC 通常要求接收方将信息持久化到稳定存储,故广播时间可能在 0.5ms 到 20ms 之间,取决于存储技术。因此选举超时可能在 10ms 到 500ms 之间。典型服务器 MTBF 为数月或更长,容易满足该时间要求。
## 6 集群成员变更
此前我们假定集群配置(参与共识算法的服务器集合)固定。实践中偶尔需要变更配置,例如在服务器故障时替换或改变复制度。虽然可以通过将整个集群下线、更新配置文件再重启来完成,这会使集群在变更期间不可用。此外,若有任何人工步骤,会带来操作错误风险。为避免这些问题,我们决定将配置变更自动化并纳入 Raft 共识算法。
为使配置变更机制安全,在过渡期间任何时刻都不能出现同一 term 内选出两名 leader 的可能。不幸的是,服务器直接从旧配置切换到新配置的任何做法都不安全。无法原子地同时切换所有服务器,因此过渡期间集群可能分裂成两个独立的多数派(见图 10
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021838926-f53e2570b0b2ca02989c125651f14c9e.png)
**图 10** 直接从一种配置切换到另一种不安全,因为不同服务器会在不同时刻切换。本例中集群从三台扩展到五台。不幸的是,存在某一时刻可能在同一 term 内选出两名不同 leader一个拥有旧配置C_old的多数另一个拥有新配置C_new的多数。
为确保安全,配置变更必须采用两阶段方式。在 Raft 中集群先切换到我们称为联合共识joint consensus的过渡配置联合共识被提交后系统再过渡到新配置。联合共识同时包含旧配置与新配置
- 日志条目被复制到两种配置中的全部服务器。
- 两种配置中的任意服务器均可担任 leader。
- 达成一致(选举与条目提交)需要旧配置与新配置各自的多数派同时同意。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772021883284-7d2da950b84ae2e67410d9e088520623.png)
**图 11** 配置变更时间线。虚线表示已创建但未提交的配置条目实线表示最新已提交的配置条目。Leader 先在日志中创建 C_old,new 配置条目并提交到 C_old,newC_old 的多数与 C_new 的多数)。然后创建 C_new 条目并提交到 C_new 的多数。不存在 C_old 与 C_new 能独立做决策的时刻。
联合共识允许各服务器在不同时刻完成配置过渡而不影响安全。此外,联合共识使集群能在整个配置变更期间继续服务客户端请求。
集群配置通过复制日志中的特殊条目存储与传播;图 11 展示了配置变更过程。当 leader 收到从 C_old 变更到 C_new 的请求时,将联合共识的配置(图中为 C_old,new作为日志条目存储并用前述机制复制该条目。某服务器一旦将新配置条目加入其日志就将其用于之后所有决策服务器始终使用其日志中最新配置不论该条目是否已提交。这意味着 leader 将用 C_old,new 的规则判断 C_old,new 的日志条目何时被提交。若 leader 崩溃,新 leader 可能在 C_old 或 C_old,new 下选出,取决于获胜 candidate 是否已收到 C_old,new。无论如何此期间 C_new 不能单独做决策。
一旦 C_old,new 被提交C_old 与 C_new 都不能在未经对方同意下做决策,且 Leader Completeness Property 保证只有带有 C_old,new 日志条目的服务器能被选为 leader。此时 leader 可以安全地创建描述 C_new 的日志条目并复制到集群。同样,该配置在每台服务器看到时即生效。当新配置按 C_new 的规则被提交后,旧配置不再相关,不在新配置中的服务器可被关闭。如图 11不存在 C_old 与 C_new 能同时独立做决策的时刻;这保证了安全。
重新配置还有三个问题需要处理。第一新服务器最初可能不存储任何日志条目。若在此状态下加入集群它们可能需要很长时间才能赶上期间可能无法提交新日志条目。为避免可用性缺口Raft 在配置变更前增加一个阶段新服务器以无投票权成员身份加入集群leader 向它们复制日志条目,但不计入多数)。一旦新服务器赶上集群其余部分,即可按上述进行重新配置。
第二,集群 leader 可能不在新配置中。此时 leader 在提交 C_new 日志条目后卸任(恢复为 follower。这意味着会有一段时间在提交 C_new 期间leader 在管理一个不包含自己的集群它复制日志条目但不把自己算入多数。Leader 交接在 C_new 被提交时发生,因为这是新配置能独立运作的最早时刻(从 C_new 中总能选出 leader。在此之前可能只有 C_old 中的服务器能被选为 leader。
第三,被移除的服务器(不在 C_new 中的)可能干扰集群。这些服务器收不到心跳,会超时并发起新选举,随后发送带新 term 的 RequestVote RPC导致当前 leader 恢复为 follower。最终会选出新 leader但被移除的服务器会再次超时过程重复导致可用性变差。为防止此问题服务器在认为当前存在 leader 时忽略 RequestVote RPC。具体地若某服务器在收到当前 leader 消息后的最小选举超时内收到 RequestVote RPC不更新其 term 也不授予投票。这不影响正常选举,因为每台服务器在发起选举前至少等待最小选举超时。但这有助于避免被移除服务器的干扰:若 leader 能向集群发送心跳,就不会被更大的 term 罢免。
## 7 日志压缩
Raft 的日志在正常运行中会增长以容纳更多客户端请求,但在实用系统中不能无限增长。随着日志变长,占用更多空间且回放更耗时,最终会在没有丢弃日志中积累的过时信息的机制时导致可用性问题。
快照snapshotting是最简单的压缩方式。在快照中将整个当前系统状态写入稳定存储上的快照然后丢弃该点之前的全部日志。Chubby 与 ZooKeeper 使用快照,本节余下部分描述 Raft 中的快照。
增量压缩方式如 log cleaning [36] 与 log-structured merge trees [30, 5] 也可行。它们每次只处理一部分数据使压缩负载随时间更均匀分布。它们先选择积累了大量已删除与覆盖对象的数据区域将该区域中的存活对象更紧凑地重写并释放该区域。与快照相比这需要大量额外机制与复杂度快照通过始终针对整个数据集操作来简化问题。Log cleaning 需要对 Raft 做修改,而 state machine 可用与快照相同的接口实现 LSM tree。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772022049036-d2ace8d86fdd4a0b58434145815a6b08.png)
**图 12** 某服务器用新快照替换其日志中已提交的条目index 1 到 5快照仅存储当前状态本例中为变量 x 和 y。快照的 last included index 与 term 用于将快照定位于条目 6 之前的日志中。
图 12 展示了 Raft 中快照的基本思路。每台服务器独立制作快照,仅覆盖其日志中已提交的条目。主要工作由 state machine 将其当前状态写入快照完成。Raft 还在快照中包含少量元数据last included index 是快照所替换的日志中最后一条的 index即 state machine 已应用的最后一条last included term 是该条目的 term。这些被保留以支持快照之后第一条日志条目的 AppendEntries 一致性检查,因为该条目需要前一条的 log index 与 term。为支持集群成员变更第 6 节),快照还包含截至 last included index 的日志中的最新配置。服务器完成快照写入后,可删除 last included index 及之前的所有日志条目以及任何更早的快照。
尽管服务器通常独立制作快照leader 偶尔需要向落后的 follower 发送快照。当 leader 已丢弃需要发给某 follower 的下一条日志条目时会发生这种情况。Leader 使用名为 InstallSnapshot 的新 RPC 向落后过多的 follower 发送快照;见图 13。Follower 收到该 RPC 的快照后,必须决定如何处理其现有日志条目。通常快照会包含接收方日志中尚未有的新信息,此时 follower 丢弃其全部日志;它们都被快照取代,且可能含有与快照冲突的未提交条目。若 follower 收到描述其日志前缀的快照(因重传或错误),则删除快照覆盖的日志条目,但快照之后的条目仍然有效且必须保留。
> InstallSnapshot RPC
>
>
> *由 leader 调用以向 follower 发送快照的分片。*
>
> *Leader 始终按序发送分片。*
>
>
> **参数:**
>
> `term`: leader 的 term
>
> `leaderId`: 便于 follower 将客户端重定向到 leader
>
> `lastIncludedIndex`: 快照替换的直至并包含该 index 的所有条目
>
> `lastIncludedTerm`: lastIncludedIndex 的 term
>
> `offset`: 分片在快照文件中的字节偏移
>
> `data[]`: 从 offset 开始的快照分片原始字节
>
> `done`: 若此为最后一块则为 true
>
>
> **返回:**
>
> `term`: currentTerm供 leader 更新自身
>
>
> **接收者实现:**
>
> 1. 若 term < currentTerm 则立即回复
>
> 2. 若是第一块offset 为 0则创建新快照文件
>
> 3. 在给定 offset 处将 data 写入快照文件
>
> 4. 若 done 为 false 则回复并等待更多 data 分片
>
> 5. 保存快照文件,丢弃任何 index 更小的已有或部分快照
>
> 6. 若已有日志条目与快照的 last included 条目 index 和 term 相同,保留其后的日志条目并回复
>
> 7. 丢弃整个日志
>
> 8. 用快照内容重置 state machine并加载快照的集群配置
>
**图 13** InstallSnapshot RPC 摘要。快照被分成块传输;每块都给 follower 一个存活的信号,使其能重置选举定时器。
这种快照方式偏离了 Raft 的强 leader 原则,因为 follower 可在 leader 不知情的情况下制作快照。但我们认为这种偏离是合理的。Leader 有助于在达成共识时避免冲突决策,而快照时共识已经达成,故没有决策冲突。数据仍只从 leader 流向 follower只是 follower 现在可以重组其数据。
还有两个影响快照性能的问题。第一,服务器必须决定何时制作快照。若过于频繁会浪费磁盘带宽与能耗;若过于稀疏则可能耗尽存储并在重启时增加回放日志所需时间。一种简单策略是当日志达到固定字节大小时制作快照。若该大小设得比预期快照大小大不少,快照的磁盘带宽开销会较小。第二,写快照可能耗时较长,我们不希望这延迟正常操作。解决方法是使用 copy-on-write 技术,使新更新能在不影响正在写入的快照的情况下被接受。例如,用函数式数据结构实现的 state machine 天然支持这一点。也可使用操作系统的 copy-on-write 支持(如 Linux 的 fork为整个 state machine 创建内存快照(我们的实现采用此方式)。
## 8 客户端交互
本节描述客户端如何与 Raft 交互,包括客户端如何发现集群 leader 以及 Raft 如何支持可线性化语义 [10]。这些问题适用于所有基于共识的系统Raft 的方案与其他系统类似。
Raft 的客户端将所有请求发往 leader。客户端首次启动时连接到随机选中的服务器。若首选不是 leader该服务器会拒绝请求并提供其最近得知的 leader 信息AppendEntries 请求包含 leader 的网络地址)。若 leader 崩溃,客户端请求会超时;客户端随后随机选择服务器重试。
我们对 Raft 的目标是实现可线性化语义每个操作在其调用与响应之间的某时刻看起来被瞬时、恰好执行一次。但就目前描述而言Raft 可能多次执行同一 command例如若 leader 在提交日志条目后、响应客户端前崩溃,客户端会向新 leader 重试该 command导致其被再次执行。解决方法是让客户端为每条 command 分配唯一序列号。然后 state machine 记录每个客户端已处理的最新序列号及对应响应。若收到序列号已执行过的 command立即响应而不重新执行。
只读操作可以不写日志处理。但若没有额外措施,可能返回过期数据,因为响应请求的 leader 可能已被其不知情的新 leader 取代。可线性化读不能返回过期数据Raft 需要两条额外预防措施在不写日志的情况下保证这一点。第一leader 必须掌握哪些条目已提交的最新信息。Leader Completeness Property 保证 leader 拥有所有已提交条目,但在其 term 开始时可能不知道是哪些。要弄清这一点,需要提交其 term 的一条条目。Raft 的做法是让每个 leader 在 term 开始时向日志提交一条空白 no-op 条目。第二leader 在处理只读请求前必须检查自己是否已被罢免(若已选出更新的 leader其信息可能已过期。Raft 的做法是让 leader 在响应只读请求前与集群多数交换心跳。 Alternativelyleader 可依赖心跳机制提供某种 lease [9],但这会依赖时间假设来保证安全(假定有界时钟偏差)。
## 9 实现与评估
我们将 Raft 实现为 RAMCloud [33] 的复制 state machine 的一部分,用于存储配置信息并协助 RAMCloud coordinator 的故障转移。Raft 实现约 2000 行 C++ 代码,不含测试、注释与空行。源代码可公开获取 [23]。另有约 25 个基于本文草稿、处于不同开发阶段的独立第三方开源实现 [34]。多家公司也在部署基于 Raft 的系统 [34]。
本节其余部分从可理解性、正确性与性能三方面评估 Raft。
### 9.1 可理解性
为衡量 Raft 相对 Paxos 的可理解性,我们使用斯坦福大学高级操作系统课程与加州大学伯克利分校分布式计算课程的高年级本科生与研究生进行了实验。我们录制了 Raft 与 Paxos 的视频讲座并制作了对应测验。Raft 讲座覆盖本文除日志压缩外的内容Paxos 讲座覆盖了构建等价复制 state machine 所需的材料,包括 single-decree Paxos、multi-decree Paxos、重新配置以及实践中需要的若干优化如 leader 选举)。测验既考察对算法的基本理解,也要求学生推理边界情况。每名学生观看一个视频、参加对应测验,再观看第二个视频、参加第二个测验。约一半参与者先做 Paxos 部分、另一半先做 Raft 部分,以兼顾个体差异与先做部分带来的经验。我们比较了参与者在两次测验上的得分,以判断是否对 Raft 表现更好。
我们尽量使 Paxos 与 Raft 的对比公平。实验在两方面有利于 Paxos43 名参与者中有 15 人报告有 Paxos 先验经验,且 Paxos 视频比 Raft 视频长 14%。如表 1 所示,我们采取了措施缓解潜在偏差来源。所有材料可供审阅 [28, 31]。
平均而言,参与者在 Raft 测验上比 Paxos 测验高 4.9 分(满分 60Raft 平均 25.7Paxos 平均 20.8);图 14 展示了个人得分。配对 t 检验表明,在 95% 置信度下Raft 得分的真实分布均值至少比 Paxos 高 2.5 分。
我们还建立了线性回归模型基于三个因素预测新学生的测验得分参加哪个测验、Paxos 先验经验程度、学习算法的顺序。模型预测测验选择会产生有利于 Raft 的 12.5 分差异。这显著高于观测到的 4.9 分,因为许多学生有 Paxos 先验经验,这对 Paxos 帮助较大、对 Raft 略小。有趣的是,模型还预测先参加 Paxos 测验的人在 Raft 上得分低 6.3 分;尽管原因不明,这在统计上似乎显著。
我们还在测验后调查了参与者认为哪种算法更容易实现或解释;结果见图 15。绝大多数参与者认为 Raft 更容易实现和解释41 人中有 33 人分别对两个问题如此回答。但这些自我报告可能不如测验得分可靠且参与者可能因知晓我们“Raft 更易理解”的假设而产生偏差。Raft 用户研究的详细讨论见 [31]。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772022483568-9694dd588fcc9fe6bbcefc19617daf84.png)
**图 14** 43 名参与者在 Raft 与 Paxos 测验上表现的散点图。对角线上方33 人)表示 Raft 得分更高的参与者。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772022592802-46c5604e14d4c7bebb3718567ed2cfc5.png)
**图 15** 使用 5 点量表,参与者被问及(左)哪种算法在实现一个正确、高效的系统时更容易实现,(右)哪种更容易向 CS 研究生解释。
### 9.2 正确性
我们为第 5 节描述的共识机制开发了形式化规范与安全性证明。形式化规范 [31] 使用 TLA+ 规范语言 [17] 将图 2 中的信息完全精确化。其约 400 行,作为证明的对象。对任何实现 Raft 的人也有独立价值。我们使用 TLA 证明系统 [7] 机械化证明了 Log Completeness Property。但该证明依赖未经机械化检查的不变量例如我们未证明规范的类型安全。此外我们撰写了 State Machine Safety 的非形式证明 [31],其是完整的(仅依赖规范)且相对精确(约 3500 词)。
**表 1** 研究中可能对 Paxos 不利的顾虑、为缓解每项所采取的措施,以及可审阅的补充材料。
| 顾虑 | 缓解偏差的措施 | 可审阅材料 [28, 31] |
|--------|-----------------------------|------------------------------|
| 讲座质量相当 | 同一讲师。Paxos 讲座基于并改进多所大学使用的现有材料。Paxos 讲座长 14%。 | videos |
| 测验难度相当 | 按难度分组并在两套试卷间配对题目。 | quizzes |
| 评分公平 | 使用评分标准。随机顺序评分,两套测验交替。 | rubric |
### 9.3 性能
Raft 的性能与 Paxos 等共识算法相当。最重要的性能场景是已确立的 leader 在复制新日志条目时。Raft 以最少的消息数实现这一点(从 leader 到半数集群的单次往返)。也可进一步优化 Raft 性能,例如它容易支持批处理与流水线以提高吞吐、降低延迟。文献中针对其他算法提出了多种优化,其中许多可应用于 Raft我们留作未来工作。
我们使用 Raft 实现测量了其 leader 选举算法的性能并回答两个问题第一选举过程是否快速收敛第二leader 崩溃后能达到的最小停机时间是多少?为测量 leader 选举,我们反复使五台服务器集群的 leader 崩溃,并计时检测崩溃与选出新 leader 所需时间(见图 16。图 16 上图表明选举超时中少量随机化就足以避免选举中的选票分散。在没有随机性时,我们的测试中 leader 选举持续超过 10 秒,因多次选票分散。仅增加 5ms 随机性就有显著帮助,中位停机时间 287ms。更多随机性改善最坏情况50ms 随机性时1000 次试验的最坏完成时间为 513ms。图 16 下图表明可通过减小选举超时来减少停机时间。在 1224ms 选举超时下,平均仅需 35ms 即可选出 leader最长一次 152ms。但将超时降得过低会违反 Raft 的时间要求leader 难以在其他服务器发起新选举前广播心跳,可能导致不必要的 leader 更替并降低整体可用性。我们建议使用保守的选举超时如 150300ms此类超时不太会引起不必要的 leader 更替,仍能提供良好可用性。
![image.png](https://list.rc707blog.top/d/local/file/imagebed/1772022674458-44c59f2393d95453d9a58485138b5979.png)
**图 16** 检测并替换崩溃 leader 所需时间。上图变化选举超时中的随机量,下图缩放最小选举超时。每条线代表 1000 次试验“150150ms”为 100 次对应一种选举超时选择例如“150155ms”表示选举超时在 150ms 与 155ms 间均匀随机选择。测量在五台服务器、广播时间约 15ms 的集群上进行。九台服务器集群结果类似。
## 10 相关工作
有大量与共识算法相关的文献,多属以下类别之一:
- Lamport 对 Paxos 的原始描述 [15],以及用更清晰方式解释的尝试 [16, 20, 21]。
- 对 Paxos 的细化,填补缺失细节并修改算法以为实现提供更好基础 [26, 39, 13]。
- 实现共识算法的系统,如 Chubby [2, 4]、ZooKeeper [11, 12]、Spanner [6]。Chubby 与 Spanner 的算法未详细公开,尽管二者均称基于 Paxos。ZooKeeper 的算法公开较详细,但与 Paxos 差异很大。
- 可应用于 Paxos 的性能优化 [18, 19, 3, 25, 1, 27]。
- Oki 与 Liskov 的 Viewstamped Replication (VR),与 Paxos 大约同时发展的另一种共识思路。原始描述 [29] 与分布式事务协议交织,但核心共识协议在近期更新 [22] 中已分离。VR 采用与 Raft 有许多相似之处的基于 leader 的方式。
Raft 与 Paxos 的最大区别在于 Raft 的强领导Raft 将 leader 选举作为共识协议的核心部分,并尽可能将功能集中在 leader。这得到更简单、更易理解的算法。例如在 Paxos 中leader 选举与基本共识协议正交仅作为性能优化并非达成共识所必需。但这带来额外机制Paxos 既有基本共识的两阶段协议,又有独立的 leader 选举机制。相比之下Raft 将 leader 选举直接纳入共识算法并作为共识两阶段中的第一阶段,机制少于 Paxos。
与 Raft 一样VR 与 ZooKeeper 也是基于 leader 的,因此共享 Raft 相对 Paxos 的许多优势。但 Raft 的机制少于 VR 或 ZooKeeper因为它最小化非 leader 的功能。例如 Raft 中日志条目只沿一个方向流动:在 AppendEntries RPC 中从 leader 向外。VR 中日志条目双向流动leader 在选举过程中可接收日志条目带来额外机制与复杂度。ZooKeeper 的公开描述也在与 leader 之间双向传输日志条目,但实现显然更接近 Raft [35]。
Raft 的消息类型少于我们所知的任何其他基于共识的日志复制算法。例如我们统计了 VR 与 ZooKeeper 用于基本共识与成员变更的消息类型排除日志压缩与客户端交互因它们与算法几乎独立。VR 与 ZooKeeper 各定义 10 种消息类型,而 Raft 仅有 4 种(两种 RPC 请求及其响应。Raft 的消息略比其它算法密集,但总体上更简单。
Raft 的强领导方式简化了算法,但排除了部分性能优化。例如 Egalitarian Paxos (EPaxos) 在某些条件下能以无 leader 方式获得更高性能 [27]。EPaxos 利用 state machine command 的可交换性。只要与某 command 并发提出的其他 command 与之可交换,任意服务器只需一轮通信即可提交该 command。但若并发提出的 command 彼此不可交换EPaxos 需要额外一轮通信。因任意服务器都可提交 commandEPaxos 在服务器间负载均衡良好,在 WAN 环境下能达到比 Raft 更低的延迟,但显著增加了 Paxos 的复杂度。
其他工作中提出或实现了多种集群成员变更方式,包括 Lamport 的原始提议 [15]、VR [22]、SMART [24]。我们为 Raft 选择联合共识是因为它利用共识协议的其余部分成员变更几乎不需要额外机制。Lamport 的基于 α 的方式不适用于 Raft因其假定无需 leader 即可达成共识。与 VR 和 SMART 相比Raft 的重新配置算法的优势是成员变更可在不限制正常请求处理的情况下进行相反VR 在配置变更期间停止所有正常处理SMART 对未完成请求数量施加类 α 限制。Raft 的方式也比 VR 或 SMART 增加更少机制。
## 11 结论
算法常以正确性、效率和/或简洁为主要目标。尽管这些目标都值得追求,我们相信可理解性同样重要。在开发者将算法落实为实用实现之前,其他目标都无法实现,而实现必然会在已发表形式基础上偏离与扩展。除非开发者对算法有深入理解并能形成直觉,否则很难在实现中保留其理想性质。
本文针对分布式共识问题:被广泛接受却难以理解的 Paxos 多年来一直困扰学生与开发者。我们提出了一种新算法 Raft并表明其比 Paxos 更易理解。我们也相信 Raft 为系统构建提供了更好基础。以可理解性为主要设计目标改变了我们设计 Raft 的方式;随着设计推进,我们反复运用少数几种技术,如问题分解与状态空间简化。这些技术不仅提高了 Raft 的可理解性,也使我们更容易确信其正确性。
## 12 致谢
若无 Ali Ghodsi、David Mazières 以及伯克利 CS 294-91 与斯坦福 CS 240 学生的支持用户研究无法完成。Scott Klemmer 帮助我们设计用户研究Nelson Ray 在统计分析上提供建议。用户研究中的 Paxos 幻灯片大量借鉴了 Lorenzo Alvisi 最初制作的讲稿。特别感谢 David Mazières 与 Ezra Hoch 发现 Raft 中的微妙错误。许多人就论文与用户研究材料提供了有益反馈,包括 Ed Bugnion, Michael Chan, Hugues Evrard, Daniel Giffin, Arjun Gopalan, Jon Howell, Vimalkumar Jeyakumar, Ankita Kejriwal, Aleksandar Kracun, Amit Levy, Joel Martin, Satoshi Matsushita, Oleg Pesok, David Ramos, Robbert van Renesse, Mendel Rosenblum, Nicolas Schiper, Deian Stefan, Andrew Stone, Ryan Stutsman, David Terei, Stephen Yang, Matei Zaharia24 位匿名会议审稿人(含重复),以及我们的 shepherd Eddie Kohler。Werner Vogels 在推特上转发了早期草稿链接,为 Raft 带来了大量关注。本工作由 Gigascale Systems Research Center 与 Multiscale Systems Center半导体研究公司 Focus Center Research Program 资助的六个研究中心中的两个、STARnetMARCO 与 DARPA 资助的半导体研究公司项目)、美国国家科学基金会(资助号 0963859以及 Facebook、Google、Mellanox、NEC、NetApp、SAP、Samsung 的资助支持。Diego Ongaro 受 The Junglee Corporation Stanford Graduate Fellowship 资助。
## 参考文献
[1] BOLOSKY, W. J., BRADSHAW, D., HAAGENS, R. B., KUSTERS, N. P., AND LI, P. Paxos replicated state machines as the basis of a high-performance data store. In Proc. NSDI'11, USENIX Conference on Networked Systems Design and Implementation (2011), USENIX, pp. 141154.
[2] BURROWS, M. The Chubby lock service for loosely-coupled distributed systems. In Proc. OSDI'06, Symposium on Operating Systems Design and Implementation (2006), USENIX, pp. 335350.
[3] CAMARGOS, L. J., SCHMIDT, R. M., AND PEDONE, F. Multicoordinated Paxos. In Proc. PODC'07, ACM Symposium on Principles of Distributed Computing (2007), ACM, pp. 316317.
[4] CHANDRA, T. D., GRIESEMER, R., AND REDSTONE, J. Paxos made live: an engineering perspective. In Proc. PODC'07, ACM Symposium on Principles of Distributed Computing (2007), ACM, pp. 398407.
[5] CHANG, F., DEAN, J., GHEMAWAT, S., HSIEH, W. C., WALLACH, D. A., BURROWS, M., CHANDRA, T., FIKES, A., AND GRUBER, R. E. Bigtable: a distributed storage system for structured data. In Proc. OSDI'06, USENIX Symposium on Operating Systems Design and Implementation (2006), USENIX, pp. 205218.
[6] CORBETT, J. C., DEAN, J., EPSTEIN, M., FIKES, A., FROST, C., FURMAN, J. J., GHEMAWAT, S., GUBAREV, A., HEISER, C., HOCHSCHILD, P., HSIEH, W., KANTHAK, S., KOGAN, E., LI, H., LLOYD, A., MELNIK, S., MWAURA, D., NAGLE, D., QUINLAN, S., RAO, R., ROLIG, L., SAITO, Y., SZYMANIAK, M., TAYLOR, C., WANG, R., AND WOODFORD, D. Spanner: Google's globally-distributed database. In Proc. OSDI'12, USENIX Conference on Operating Systems Design and Implementation (2012), USENIX, pp. 251264.
[7] COUSINEAU, D., DOLIGEZ, D., LAMPORT, L., MERZ, S., RICKETTS, D., AND VANZETTO, H. TLA+ proofs. In Proc. FM'12, Symposium on Formal Methods (2012), D. Giannakopoulou and D. M´ery, Eds., vol. 7436 of Lecture Notes in Computer Science, Springer, pp. 147154.
[8] GHEMAWAT, S., GOBIOFF, H., AND LEUNG, S.-T. The Google file system. In Proc. SOSP'03, ACM Symposium on Operating Systems Principles (2003), ACM, pp. 2943.
[9] GRAY, C., AND CHERITON, D. Leases: An efficient fault-tolerant mechanism for distributed file cache consistency. In Proceedings of the 12th ACM Symposium on Operating Systems Principles (1989), pp. 202210.
[10] HERLIHY, M. P., AND WING, J. M. Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems 12 (July 1990), 463492.
[11] HUNT, P., KONAR, M., JUNQUEIRA, F. P., AND REED, B. ZooKeeper: wait-free coordination for internet-scale systems. In Proc ATC'10, USENIX Annual Technical Conference (2010), USENIX, pp. 145158.
[12] JUNQUEIRA, F. P., REED, B. C., AND SERAFINI, M. Zab: High-performance broadcast for primary-backup systems. In Proc. DSN'11, IEEE/IFIP Int'l Conf. on Dependable Systems & Networks (2011), IEEE Computer Society, pp. 245256.
[13] KIRSCH, J., AND AMIR, Y. Paxos for system builders. Tech. Rep. CNDS-2008-2, Johns Hopkins University, 2008.
[14] LAMPORT, L. Time, clocks, and the ordering of events in a distributed system. Communications of the ACM 21, 7 (July 1978), 558565.
[15] LAMPORT, L. The part-time parliament. ACM Transactions on Computer Systems 16, 2 (May 1998), 133169.
[16] LAMPORT, L. Paxos made simple. ACM SIGACT News 32, 4 (Dec. 2001), 1825.
[17] LAMPORT, L. Specifying Systems, The TLA+ Language and Tools for Hardware and Software Engineers. Addison-Wesley, 2002.
[18] LAMPORT, L. Generalized consensus and Paxos. Tech. Rep. MSR-TR-2005-33, Microsoft Research, 2005.
[19] LAMPORT, L. Fast paxos. Distributed Computing 19, 2 (2006), 79103.
[20] LAMPSON, B. W. How to build a highly available system using consensus. In Distributed Algorithms, O. Babaoğlu and K. Marzullo, Eds. Springer-Verlag, 1996, pp. 117.
[21] LAMPSON, B. W. The ABCD's of Paxos. In Proc. PODC'01, ACM Symposium on Principles of Distributed Computing (2001), ACM, pp. 1313.
[22] LISKOV, B., AND COWLING, J. Viewstamped replication revisited. Tech. Rep. MIT-CSAIL-TR-2012-021, MIT, July 2012.
[23] LogCabin source code. http://github.com/logcabin/logcabin.
[24] LORCH, J. R., ADYA, A., BOLOSKY, W. J., CHAIKEN, R., DOUCEUR, J. R., AND HOWELL, J. The SMART way to migrate replicated stateful services. In Proc. EuroSys'06, ACM SIGOPS/EuroSys European Conference on Computer Systems (2006), ACM, pp. 103115.
[25] MAO, Y., JUNQUEIRA, F. P., AND MARZULLO, K. Mencius: building efficient replicated state machines for WANs. In Proc. OSDI'08, USENIX Conference on Operating Systems Design and Implementation (2008), USENIX, pp. 369384.
[26] MAZIÈRES, D. Paxos made practical. http://www.scs.stanford.edu/~dm/home/papers/paxos.pdf, Jan. 2007.
[27] MORARU, I., ANDERSEN, D. G., AND KAMINSKY, M. There is more consensus in egalitarian parliaments. In Proc. SOSP'13, ACM Symposium on Operating System Principles (2013), ACM.
[28] Raft user study. http://ramcloud.stanford.edu/~ongaro/userstudy/.
[29] OKI, B. M., AND LISKOV, B. H. Viewstamped replication: A new primary copy method to support highly-available distributed systems. In Proc. PODC'88, ACM Symposium on Principles of Distributed Computing (1988), ACM, pp. 817.
[30] O'NEIL, P., CHENG, E., GAWLICK, D., AND ONEIL, E. The log-structured merge-tree (LSM-tree). Acta Informatica 33, 4 (1996), 351385.
[31] ONGARO, D. Consensus: Bridging Theory and Practice. PhD thesis, Stanford University, 2014 (work in progress). http://ramcloud.stanford.edu/~ongaro/thesis.pdf.
[32] ONGARO, D., AND OUSTERHOUT, J. In search of an understandable consensus algorithm. In Proc ATC'14, USENIX Annual Technical Conference (2014), USENIX.
[33] OUSTERHOUT, J., AGRAWAL, P., ERICKSON, D., KOZYRAKIS, C., LEVERICH, J., MAZIÈRES, D., MITRA, S., NARAYANAN, A., ONGARO, D., PARULKAR, G., ROSENBLUM, M., RUMBLE, S. M., STRATMANN, E., AND STUTSMAN, R. The case for RAMCloud. Communications of the ACM 54 (July 2011), 121130.
[34] Raft consensus algorithm website. http://raftconsensus.github.io.
[35] REED, B. Personal communications, May 17, 2013.
[36] ROSENBLUM, M., AND OUSTERHOUT, J. K. The design and implementation of a log-structured file system. ACM Trans. Comput. Syst. 10 (February 1992), 2652.
[37] SCHNEIDER, F. B. Implementing fault-tolerant services using the state machine approach: a tutorial. ACM Computing Surveys 22, 4 (Dec. 1990), 299319.
[38] SHVACHKO, K., KUANG, H., RADIA, S., AND CHANSLER, R. The Hadoop distributed file system. In Proc. MSST'10, Symposium on Mass Storage Systems and Technologies (2010), IEEE Computer Society, pp. 110.
[39] VAN RENESSE, R. Paxos made moderately complex. Tech. rep., Cornell University, 2012.