Skip to content

Raft 算法 ​

Raft 是 Diego Ongaro 和 John Ousterhout 于 2014 年提出的分布式共识算法,目标是易于理解和实现。Raft 将共识分解为领导者选举、日志复制、安全性三个子问题,广泛应用于 etcd、Consul、TiKV。

Raft 的设计目标 ​

Raft 的设计目标是易于理解。Lamport 的 Paxos 算法虽然理论完备,但难以理解和实现。Raft 通过分解问题、限制状态空间,使得算法易于理解。

Raft 的另一个目标是工程实用。Raft 的实现相对简单,性能良好,适合作为工程实践的参考。

Raft 的角色 ​

Leader(领导者) ​

Leader 处理所有写请求,将日志复制到 Follower。Leader 是唯一的,系统中最多有一个 Leader。Leader 有一个任期(Term),任期号单调递增。

Leader 的职责:接收客户端请求、将请求追加到日志、复制日志到 Follower、提交已提交的日志、发送心跳维持 Leader 地位。

Follower(跟随者) ​

Follower 响应 Leader 的请求,包括日志复制请求和心跳请求。Follower 被动接收 Leader 的指令,不主动发起请求。

Follower 的职责:响应 Leader 的日志复制请求、响应 Leader 的心跳请求、如果长时间未收到 Leader 心跳,则转为 Candidate。

Candidate(候选人) ​

Candidate 是选举时的临时角色,候选投票给自己,请求其他节点投票。获得多数票的 Candidate 成为 Leader。

Candidate 的职责:发起选举、投票给自己、请求其他节点投票、如果获得多数票则转为 Leader、如果发现更高任期的 Leader 则转为 Follower。

Leader 选举 ​

选举触发 ​

Follower 如果长时间(election timeout,通常 150-300ms)未收到 Leader 心跳,则认为 Leader 故障,转为 Candidate,发起选举。

Candidate 增加当前任期号,投票给自己,然后向其他节点发送 RequestVote 请求。

投票规则 ​

节点收到 RequestVote 请求后,如果满足以下条件,则投票给 Candidate:Candidate 的任期号大于自己的任期号,或者相等但未投票;Candidate 的日志至少和自己一样新(日志的任期号和长度比较)。

每个节点在每个任期只能投票一次,先到先得。

选举结果 ​

如果 Candidate 获得多数票(N/2 + 1),则成为 Leader,开始发送心跳。

如果 Candidate 收到更高任期的 Leader 的心跳或 AppendEntries 请求,则转为 Follower。

如果 Candidate 的选举超时,仍未获得多数票,则开始新一轮选举(增加任期号)。

选举安全性 ​

选举安全性:每个任期最多有一个 Leader 被选出。这是因为投票规则保证了只有拥有最新日志的 Candidate 才能获得多数票。

Leader 完整性:如果日志条目在某任期被提交,那么该条目会出现在所有更高任期的 Leader 的日志中。这是因为新 Leader 必须拥有所有已提交的日志条目。

日志复制 ​

日志结构 ​

日志由有序的日志条目组成,每个条目包含索引、任期号、命令。索引是日志条目的位置,从 1 开始。任期号是 Leader 提出该条目时的任期。命令是状态机的操作(如 SET x = 1)。

复制流程 ​

Leader 接收客户端请求,将命令追加到本地日志,然后发送 AppendEntries 请求给 Follower。AppendEntries 请求包含:任期号、前一个日志条目的索引和任期、新日志条目、当前提交索引。

Follower 收到 AppendEntries 请求后,如果前一个日志条目匹配(索引和任期都匹配),则追加新日志条目;否则拒绝。

如果 Follower 的日志与 Leader 不一致(前一个日志条目不匹配),Leader 会递减前一个日志条目的索引,直到找到匹配的条目,然后覆盖 Follower 冲突的日志条目。

日志提交 ​

当日志条目被多数派(N/2 + 1)复制,Leader 认为该条目已提交,可以应用到状态机。Leader 在后续的 AppendEntries 请求中携带提交索引,通知 Follower 提交已提交的日志条目。

日志匹配特性 ​

如果两个日志包含相同的索引和任期号,那么该索引之前的所有日志条目都相同。这是因为 AppendEntries 请求只在匹配时才追加日志,覆盖不一致的日志。

日志压缩 ​

日志无限增长会占用大量磁盘空间。Raft 通过快照(Snapshot)压缩日志。快照是状态机在某个时刻的完整状态,包含最后应用的日志索引和任期。

Leader 定期创建快照,丢弃快照之前的日志。Follower 如果落后太多,Leader 可以发送快照而不是日志条目。

快照的问题:快照创建需要时间,会阻塞状态机。快照传输需要网络带宽。快照的安装需要时间,会阻塞日志复制。

Raft 的安全性 ​

选举安全性 ​

每个任期最多有一个 Leader 被选出。这是因为投票规则保证了只有拥有最新日志的 Candidate 才能获得多数票。

Leader 完整性 ​

如果日志条目在某任期被提交,那么该条目会出现在所有更高任期的 Leader 的日志中。这是因为新 Leader 必须拥有所有已提交的日志条目。

日志匹配性 ​

如果两个日志包含相同的索引和任期号,那么该索引之前的所有日志条目都相同。这是因为 AppendEntries 请求只在匹配时才追加日志。

领导者完备性 ​

如果某个日志条目在某个任期被提交,那么该条目会出现在所有后续任期的 Leader 的日志中。这是因为新 Leader 必须获得多数票,而多数派中必然包含拥有该条目的节点。

Raft 的实现 ​

Raft 的实现相对简单,但有细节需要注意。心跳是空的 AppendEntries 请求,用于维持 Leader 地位。选举超时是随机的(150-300ms),避免同时发起选举导致分裂投票。日志一致性检查通过前一个日志条目的索引和任期实现,确保日志匹配。

Raft 的优化 ​

成员变更 ​

Raft 的成员变更需要安全地增加或删除节点。直接变更可能导致两个 Leader 同时存在(分裂脑)。联合一致性(Joint Consensus)是 Raft 的成员变更方法,通过两阶段变更实现。

优先级选举 ​

Raft 可以给节点设置优先级,优先级高的节点更容易成为 Leader。这可以用于将 Leader 迁移到性能更好的节点。

只读请求 ​

如果只需要读操作,可以绕过 Leader,直接从 Follower 读取。但需要保证读到已提交的数据,可以通过 Leader 线性一致性读或租约机制实现。

Raft 的应用 ​

etcd:CoreOS 的分布式键值存储,用于服务发现、配置管理。etcd 使用 Raft 保证数据一致性。

Consul:HashiCorp 的服务网格工具,用于服务发现、配置管理。Consul 使用 Raft 保证一致性。

TiKV:PingCAP 的分布式事务型键值数据库,使用 Raft 实现数据复制。

Raft 是工程实践的共识算法,易于理解和实现,性能良好。理解 Raft 有助于理解分布式系统的协调问题。