分布式共识-Raft协议分析
Raft 协议是分布式系统中最重要的共识算法之一,被广泛应用于 etcd、TiDB、CockroachDB 等知名项目中。Raft 的核心设计目标就是易于理解(Understandability),它通过将复杂的一致性问题分解为几个相对独立的子问题来实现这一目标。Raft 算法是对“状态机复制” (SMR) 模型一个非常清晰和易于理解的实现。

Raft 的重要特性:
- 一致性:Raft 保证集群中所有节点的数据一致性。日志条目一旦被提交并且被大多数节点复制,便成为最终一致的。
- 简易性:Raft 的设计比 Paxos 更直观,易于理解和实现。通过引入明确的领导者角色和日志复制机制,Raft 确保了系统的一致性。
- 高可用性:Raft 通过选举机制和日志复制机制保证系统在节点发生故障时依然能够继续工作。
- 可扩展性:Raft 适用于较大的分布式系统,能够在多个节点之间保持一致性,并且在节点增加时,系统能够扩展。
核心概念
Raft 通过一个有领导者的协议来保证一致性,并通过心跳机制和日志复制确保所有节点最终达成一致。Raft 的核心思想可以概括为:一个强一致的领导者(Leader)控制日志的提交和复制,集群中的节点通过领导者来保证一致性。在Raft算法中定义了节点角色或状态:
- Leader:负责管理集群,处理客户端请求,向其他节点(Follower)复制日志条目以及状态机应用,所有客户端请求都会先发送到 Leader。同时,Leader会定期向所有 Follower 发送心跳 (Heartbeat),以维持其领导地位并告知 Follower 自己“还活着”。
- Follower:完全被动节点,接受 Leader 的命令。它不处理客户端请求,所有请求都重定向到 Leader。唯一的职责是响应 Leader 和 Candidate 的 RPC 请求(如接收日志、投票)。另外,如果Election Timeout周期没有收到 Leader 的心跳,它会认为 Leader 已宕机,并转变为 Candidate。
- Candidate:选举过程中的临时角色。在选举过程中,如果一个Follower节点没有接收到 Leader 的心跳消息,它就会变成 Candidate,向集群中的其他服务器发送投票请求 (RequestVote),如果它获得了超过半数 (Majority) 的选票,它就晋升为 Leader。

在一个 Raft 集群中,任何时候都有且仅有一个服务器是领导者 (Leader)。所有的数据变更(写操作)都必须经过 Leader。Leader 负责接收客户端的请求,将其作为日志条目(Log Entry)复制到其他跟随者 (Follower) 节点,并在“安全”的时候通知 Follower 将日志条目应用到它们的状态机。
Raft集群中Leader生命周期被划分为一个个连续的、单调递增的任期(Term),它充当了集群的逻辑时钟。每个任期从一次选举开始,成功选举出的领导者在任期内负责管理集群。任期的作用:
- 识别过时的 Leader:如果一个 Leader 发现来自其他服务器的 RPC 中(无论是投票请求还是日志请求)包含一个比自己当前任期号更新 (更大) 的任期号,它会立即降级 (Step Down) 为 Follower。
- 拒绝过时的 Candidate:如果一个 Follower 收到一个任期号比自己旧 (更小) 的投票请求,它会直接拒绝。
Raft算法原理分析
Raft算法是Multi-Paxos算法的变种,与其类似在集群运行时会选取一个Leader节点,当集群稳定后Leader负责管理集群,处理客户端请求,同时向其他节点(Follower)复制日志条目以及状态机应用,当Leader节点失联后会选取新的Leader实现集群的容错性。
领导者选举 (Leader Election)
Raft 中每个节点都有三种状态:Leader、Follower 和 Candidate。初始状态为 Follower。Leader 负责接收客户端请求,并通过日志复制将命令传递给集群中的其他节点。如果一个 Follower 长时间(随机化的时间)没有接收到 Leader 的心跳(心跳是 Leader 定期发送的消息,用来维持集群的健康),会发起选举。
Leader选举流程
- 当前节点状态由Follower变为Candidate。
- 由Candidate 节点发起选举,它会为自己投票,增加自己的任期编号,并将选票请求(请求选举)广播给其他节点。
- 每个 Follower 节点收到请求后,会根据当前日志的最新情况决定是否投票给该 Candidate。
- 如果该节点的日志版本更高,它会投票给自己,否则投票给请求的候选人。
- 任期编号相同时,日志完整性高的Follower(也就是最后一条日志项对应的任期编号值更大,索引号更大),该Follower节点会拒绝投票给日志完整性低的Candidate。
- 等待是否成为Leader:
- Candidate 接收到了来自超过半数 (N/2 + 1) 服务器的选票,立即转变为 Leader 角色。成为 Leader 后,它立刻向所有 Follower 发送心跳包(空的 AppendEntries RPC),广播选举成功并阻止新的选举。
- 在等待投票期间,Candidate 收到了一个来自新 Leader(必须是不小于自己任期的 Leader)的 AppendEntries RPC(心跳)。响应并承认新 Leader 的合法性,立即转变为 Follower。
- 在选举超时时间内,没有 Candidate 获得过半数选票(例如,多个 Candidate 同时发起选举,导致“分票”)。Candidate 会保持 Candidate 状态,增加任期号,并发起新一轮的选举。为了避免无限期的“分票”,Raft 的“选举超时”时间必须是随机的。这确保了总有一个 Candidate 会先超时并发起选举,从而有很大概率赢得选票。
选举小结:
安全性:每次选举周期内最多只允许一个Leader被选举出:
- 每台服务器每届只投一票(结果保存在磁盘上)
- 同一届内,两位不同的候选人不能同时获得多数票
终止性:最终必须有一位候选人获胜
- 选举超时时间随机设定在 [T, 2T] 范围内
- 通常情况下,一台服务器会在其他服务器唤醒之前超时并赢得选举
- 如果超时时间远大于广播时间,则此方法效果良好
日志复制(Log Replication)
Raft 的核心是复制状态机 (Replicated State Machine),日志就是这个复制状态机的操作记录。在Raft集群中日志是Append-Only的,除了在发生不一致时被 Leader 强行覆盖外不能被修改,集群中所有节点都以“日志”为唯一标准,确保最终执行相同的命令序列。Raft 中定义的每个Log Entry都必须包含以下关键信息:
- term (任期号):记录这个条目是由哪个任期的 Leader 创建的。这是 Raft 日志一致性检查(Log Matching Property)的核心。Leader 在发送 AppendEntries RPC 时,会用这个 term 值来和 Follower 的日志进行比对。
- command (命令):包含客户端请求的、需要被状态机执行的具体操作(例如 SET x = 5 或 DEL y)。
- 日志的索引 (Index) 通常是隐式的,即它在日志数组中的位置,但在 RPC 通信中会显式地作为参数传递。

日志复制流程
当 Leader 接收到客户端请求时,它会将请求转换为LogEntry,并将该日志同步复制到所有的 Follower 节点,每个日志都有一个唯一的索引和所属的任期(Term)。日志同步完整流程:
- 客户端请求到达领导者: 所有客户端的写请求都必须先发送给当前的领导者。
- 领导者追加到本地日志: 领导者将客户端请求封装成一个带有当前任期号和日志索引的日志条目,并追加到自己的本地日志末尾。
- 并行发送 AppendEntries RPC: 领导者并行地向所有跟随者(Follower)发送 AppendEntries RPC。这个 RPC 包含新的日志条目以及一些元信息(如领导者的任期号、前一个日志条目的索引和任期号等)。
- 跟随者响应:
- 跟随者收到 AppendEntries 请求后,首先进行一致性检查(见下文)。
- 如果检查通过,跟随者将日志条目追加到自己的日志中,并回复领导者一个成功的确认(Success: True)。
- 如果检查失败(例如,日志不匹配),跟随者回复领导者一个失败的响应(Success: False)。
- 领导者确认多数派: 领导者等待来自多数节点(N/2 + 1)的成功确认。一旦确认,领导者就知道该日志条目已经被安全复制。
- 提交并应用到状态机:
- 领导者将该日志条目标记为“已提交”(Committed),并应用到本地的状态机(如执行数据库操作)。
- 领导者回复客户端请求成功。
- 在后续的 AppendEntries RPC(包括心跳)中,领导者会通知跟随者更新它们的 commitIndex,使跟随者也能安全地应用这些已提交的日志到它们各自的状态机。
日志修复流程
Raft 算法能够自动处理节点崩溃、网络延迟等导致的日志不一致情况。修复流程也是通过 AppendEntries RPC 来实现的:
- 一致性检查: 领导者在每次发送 AppendEntries RPC 时,会包含它认为的跟随者应匹配的前一个日志条目的索引 (prevLogIndex) 及其任期号 (prevLogTerm)。
- 跟随者日志匹配: 跟随者检查自己的日志中是否存在与 prevLogIndex 和 prevLogTerm 匹配的条目。
- 如果匹配,则说明日志在该点之前是一致的,跟随者接受新的日志条目。
- 如果不匹配,跟随者拒绝该 AppendEntries 请求。
- 领导者回溯并重试: 领导者收到失败响应后,会递减记录的该跟随者的 nextIndex(下一个应发送的日志索引),并重试发送 AppendEntries RPC。
- 强制一致: 这个过程持续回溯,直到找到领导者和跟随者日志中第一个一致的点。然后,领导者会发送所有后续的日志条目,强制覆盖跟随者本地冲突的日志,使跟随者与领导者保持一致。
通过这种机制,Raft 确保了即使在出现故障和网络分区后,所有节点的日志最终都能收敛到一致的状态。
安全性保证(Safety)
Raft 算法的安全性保证确保了分布式系统在任何故障情况下都能维护数据一致性,核心目标是 永不返回错误的结果。Raft 的安全性机制设计精巧,主要围绕已提交日志的不可变性和领导者角色的正确性展开。
选举安全性 (Election Safety): 在一个给定的任期(Term)内,最多只能选举出一个领导者(Leader)。候选人只有在收到集群中多数节点的投票时才能成为领导者,即多数派原则。另外,在任期内每个节点在一个给定的任期内最多只能投出一张票。结合多数派原则和单次投票机制,即使出现网络分区导致多个候选人竞争,也不可能同时有两个候选人在同一个任期内获得多数票。
领导者只追加原则 (Leader Append-Only):领导者永远不会覆盖或删除其日志中的条目,只会在日志末尾追加新条目。当领导者发现跟随者的日志与自己不一致时,它会强制跟随者截断其错误的日志,然后从正确的一致点开始复制领导者的日志,领导者自己的日志作为“事实的来源”是不可修改的。
日志匹配原则 (Log Matching Property):确保不同节点日志的一致性起点是相同的。
- 一致性检查: 在 AppendEntries RPC 中,领导者会发送前一个日志条目的索引和任期号。
- 连锁一致性: 如果跟随者能够匹配这个前一个条目的索引和任期号,Raft 保证在该点之前的所有日志都是完全一致的。如果不匹配,日志会在该点或更早的点上存在分歧,领导者将启动修复流程。
已提交状态机一致原则 (State Machine Safety / Committed Entries Must Be Applied):如果一个日志条目已被集群中的多数节点提交,那么所有节点最终都会提交并应用该条目,并且不会有任何节点应用一个被丢弃的(未提交)日志。
- 多数派提交: 日志条目只有在被复制到多数节点后才被标记为已提交。
- 不可改变性: Raft 保证一旦一个条目被提交,它就是持久且不可更改的。随后的所有领导者都必须包含这个已提交的条目。
领导者完整性原则 (Leader Completeness Property):如果一个日志条目在某个任期内被提交了,那么后续所有任期的领导者都必须拥有这个日志条目,这个原则确保了新当选的领导者一定包含了所有先前已提交的日志,从而防止已提交数据丢失。
- 选举限制: 这是最关键的安全性机制之一。在选举过程中,Raft 只允许拥有集群中最完整日志的节点成为领导者。
- 投票要求: 候选人在请求投票时,需要提供自己的最新日志信息。投票者只有在确认候选人的日志“至少和自己一样新”(根据最新日志的任期号和索引判断)时,才会投票给它。
容错性实现
Raft 算法的容错性主要依赖于法定人数机制 (Quorum) 和领导者驱动的状态同步来实现。只要集群中多数节点正常运行并能相互通信,系统就能在少数节点发生故障、网络延迟或分区的情况下保持可用性和数据一致性。
法定人数机制(Quorum System):这是 Raft 容错的基石。所有重要的决策(如领导者选举、日志提交)都需要获得集群中多数节点的同意。 对于一个包含 (N) 个节点的集群,只要至少有 ((N/2)+1) 个节点正常工作,系统就可以容忍最多 ((N-1)/2) 个节点的故障。即使少数节点宕机,多数节点依然可以达成共识并持续提供服务,确保了系统的高可用性。
强领导者模型与任期(Term)机制:Raft 采用强领导者模型,所有数据变更都必须通过领导者。任期机制是解决潜在“脑裂”(Split-Brain,即多个节点认为自己是领导者)问题的关键,每个领导者都有一个唯一的、递增的任期号。当一个节点收到一个带有更大任期号的 RPC 请求时,它会立即更新自己的任期号并退回跟随者状态。这确保了在任何一个任期内,只会有一个领导者拥有权威,避免了冲突更新。
领导者选举与自动故障转移:当领导者失效时,Raft 能够快速自动选举出新的领导者。
- 心跳与超时: 跟随者通过心跳超时机制检测领导者故障。
- 选举过程: 如果超时,跟随者成为候选人并发起选举。获得多数票的候选人成为新领导者。
- 恢复速度: 新领导者通常能在几百毫秒内被选出,最大程度减少停机时间。
日志复制与一致性保证:Raft 确保已提交的日志永远不会丢失,即使领导者在提交后立即崩溃。
- 持久化: 关键的 Raft 状态(当前任期号、投票信息、日志条目)在响应 RPC 之前必须持久化到稳定存储(磁盘)。这确保了服务器重启后能恢复正确的状态。
- 日志匹配与修复: 领导者通过一致性检查和回溯机制(如前所述的 nextIndex 调整)自动修复跟随者不一致的日志,强制所有节点日志收敛到一致的状态。
Raft 算法通过多数派原则来容忍节点故障,通过任期机制来解决脑裂和一致性冲突,通过领导者选举实现故障转移,并通过日志持久化和自动修复确保数据的安全性和一致性。这些机制使得 Raft 成为构建可靠、容错的分布式系统的实用选择。
Raft 故障恢复原理
Raft 算法的故障恢复是其核心容错能力的一部分,它旨在确保在节点崩溃、网络分区等故障发生后,系统能够自动恢复一致性并继续提供服务。恢复过程主要依赖于领导者选举、持久化存储和日志同步机制。
领导者故障恢复:当领导者节点崩溃或网络中断时,集群会触发新的选举流程:
- 超时检测: 跟随者节点通过检测心跳超时来判断领导者是否失效。
- 触发选举: 超时的跟随者将状态转换为候选人,增加当前任期号(Term),并向其他节点请求投票。
- 选举新领导者: 获得多数节点投票的候选人成为新的领导者。
- 安全性保证: Raft 确保只有拥有最完整、最新的已提交日志的节点才能当选为领导者。这防止了已提交数据在领导者切换过程中丢失。
跟随者故障恢复(节点重启):跟随者节点的故障恢复相对简单,因为领导者会主动管理日志同步。
- 持久化状态: Raft 要求节点在响应 RPC 前将关键状态(当前任期号、已投票对象、日志条目)持久化到磁盘。当节点重启时,它会从持久化存储中恢复这些状态。
- 重新加入集群: 重启后的节点以跟随者身份重新加入集群,并联系领导者。
- 日志同步与修复: 领导者使用 AppendEntries RPC 机制自动修复该跟随者的日志,领导者通过找到与跟随者日志一致的最后一个点,并强制覆盖跟随者所有不一致的日志条目,使其日志与领导者一致。
网络分区恢复:网络分区可能导致集群分裂成多个子集。Raft 使用多数派原则来处理这种情况:
- 多数派存活: 只有在拥有多数节点的分区中,才能成功选举出领导者,并继续处理客户端请求和提交新的日志条目。
- 少数派停滞: 少数派分区中的节点无法达成多数共识,因此无法提交新的日志条目,也无法响应客户端写请求。可能会有节点尝试发起选举,但无法获得多数票。
- 网络恢复: 当网络分区愈合时,少数派分区的节点会收到来自多数派领导者的心跳或日志同步请求。它们会发现领导者的任期号更大,从而退回跟随者状态,并通过日志同步机制快速与领导者日志保持一致。
总之:Raft 算法的故障恢复核心在于:
- 快速选举:确保系统能迅速选出新的领导者以恢复可用性。
- 日志同步:领导者负责强制使所有跟随者的日志与其保持一致,修复任何不一致或缺失的日志条目。
- 多数派原则:确保在任何时候只有单一权威能提交数据,防止数据冲突或丢失。
总结
Raft 算法通过将一致性问题分解为领导者选举、日志复制和安全性三个子问题,并围绕一个强领导者模型来构建,实现了高度的可理解性和可实现性。
- 任期 (Term) 是逻辑时钟,用于识别过时的服务器。
- 领导者选举 确保只有一个 Leader,并且 Leader 拥有最新的日志。
- 日志复制 确保所有 Follower 的日志最终都与 Leader 一致,通过“日志匹配特性”和 Leader 强行覆盖来实现。
- 安全性限制 确保了数据的正确性,如“已提交”的日志永不丢失。