raft协议中一条数据写入流程
Raft 是一种分布式一致性协议,用于在多个节点之间达成一致状态,特别适用于分布式系统中管理日志复制和领导选举。以下是 Raft 协议中写入数据的详细流程,涵盖了从客户端请求到数据最终在集群中达成一致的步骤。我会尽量清晰地描述每个阶段,并结合 Raft 的核心概念(如领导者、日志复制和提交)。
Raft 协议写入数据的流程
在 Raft 中,写入数据实际上是通过日志(log)追加和复制来实现的。整个流程依赖于领导者(Leader)、跟随者(Follower)和候选者(Candidate)三种角色,其中领导者负责协调写入操作。以下是详细步骤:
1. 客户端发送写入请求
- 前提:Raft 集群中已选举出一个唯一的领导者(Leader)。客户端只会与领导者交互。
- 操作:客户端向领导者发送一个写入请求,例如“将值 X 写入键 K”。
- 细节:请求通常包含操作命令(command),例如键值对的更新。
2. 领导者追加日志条目
- 操作:领导者接收到客户端请求后,将该操作封装为一个日志条目(log entry),追加到自己的本地日志中。
- 日志条目内容:
- 索引(Index):日志条目的位置(从 1 开始递增)。
- 任期(Term):领导者当前所在的任期号,用于确保一致性。
- 命令(Command):客户端的写入操作(例如 set K=X)。
- 状态:此时日志条目是“未提交”(uncommitted),仅存在于领导者的日志中。
- 示例:
- 假设领导者当前任期为 3,日志索引为 5,客户端请求 set K=X,则追加的日志条目为:{index: 5, term: 3, command: set K=X}。
3. 领导者向跟随者复制日志
- 操作:领导者通过网络将新日志条目发送给所有跟随者(Followers),使用 AppendEntries RPC(追加条目远程过程调用)。
- AppendEntries RPC 内容:
- 新日志条目(index, term, command)。
- 前一个日志的索引和任期(prevLogIndex, prevLogTerm):用于验证跟随者的日志是否与领导者一致。
- 提交索引(commitIndex):领导者当前已提交的日志索引。
- 目标:确保所有节点的日志最终与领导者的日志一致。
- 细节:
- 领导者并行发送请求给所有跟随者。
- 每次 RPC 还包含心跳信息,用于维持领导者地位。
4. 跟随者接收和验证日志
- 操作:每个跟随者接收到 AppendEntries 请求后:
- 一致性检查:
- 检查 prevLogIndex 和 prevLogTerm 是否与自己的日志匹配。
- 如果匹配,追加新日志条目;如果不匹配,拒绝请求并通知领导者。
- 追加日志:
- 如果检查通过,跟随者在本地日志中追加新条目。
- 响应领导者:
- 返回成功或失败的响应。
- 一致性检查:
- 示例:
- 跟随者日志已有 {index: 4, term: 3},收到 {prevLogIndex: 4, prevLogTerm: 3, entry: {index: 5, term: 3, command: set K=X}},则追加成功。
- 如果跟随者日志缺失或任期不匹配,则拒绝。
5. 领导者等待多数派响应
- 操作:领导者等待集群中超过半数节点(即多数派,quorum)的确认。
- Raft 的核心一致性保证:只要多数节点成功复制了日志条目,该条目就可以安全提交。
- 规则:
- 对于包含 N 个节点的集群,多数派需要至少 (N/2) + 1 个节点。
- 示例:5 节点集群需要 3 个节点确认。
- 结果:
- 如果收到多数派成功响应,领导者标记该日志条目为“已提交”(committed)。
- 如果未达到多数派(例如网络分区或节点故障),写入失败,客户端会收到超时或错误。
6. 领导者提交日志并应用到状态机
- 操作:
- 领导者将日志条目的状态更新为“已提交”。
- 将命令应用到本地状态机(state machine),例如更新键值存储中的 K=X。
- 更新提交索引:
- 领导者更新自己的 commitIndex 为最新提交的日志索引(例如 5)。
- 通知客户端:
- 领导者向客户端返回成功响应,例如“写入完成”。
7. 领导者通知跟随者提交
- 操作:领导者在下一次 AppendEntries RPC(或心跳)中通知所有跟随者更新其 commitIndex。
- 跟随者行为:
- 接收到新的 commitIndex 后,将对应的日志条目标记为“已提交”。
- 将命令应用到自己的状态机,保持与领导者一致。
- 细节:
- 即使某些跟随者之前未收到日志条目,领导者会持续重试,确保最终一致性。
8. 确保一致性(异常处理)
- 日志不一致:
- 如果跟随者的日志与领导者不一致(例如缺失条目或任期冲突),领导者会回溯 prevLogIndex,发送更早的日志条目,直到找到一致点,然后覆盖跟随者的日志。
- 领导者故障:
- 如果领导者在提交前宕机,新选举的领导者会检查日志并继续未完成的工作。
- 未提交的条目可能被新领导者丢弃(取决于多数派是否已复制)。
完整流程图(文字版)
- 客户端 -> 领导者:发送写入请求 set K=X。
- 领导者:追加日志条目 {index: 5, term: 3, command: set K=X}。
- 领导者 -> 跟随者:发送 AppendEntries RPC。
- 跟随者:验证并追加日志,返回响应。
- 领导者:收到多数派响应,标记日志为“已提交”。
- 领导者:应用命令到状态机,返回成功给客户端。
- 领导者 -> 跟随者:通过心跳通知提交。
- 跟随者:应用已提交日志到状态机。
关键特性
- 强一致性:只有多数节点确认后,数据才算提交。
- 容错性:只要多数节点存活,集群仍可工作。
- 日志复制:通过日志确保所有节点状态最终一致。
- 领导者单点:所有写入必须经过领导者,避免冲突。
示例场景(5 节点集群)
- 初始状态:领导者日志 [1:T1, 2:T1, 3:T2, 4:T3],跟随者同步。
- 写入请求:客户端发送 set K=X。
- 步骤:
- 领导者追加 [5:T3: set K=X]。
- 发送 AppendEntries 给 4 个跟随者。
- 3 个跟随者成功响应(加上领导者自己,共 4 个,超过半数)。
- 领导者提交 [5:T3],应用 K=X,通知客户端。
- 后续心跳通知其他跟随者提交。
注意事项
- 性能:写入需要等待多数派响应,延迟可能高于单节点系统。
- 网络分区:如果领导者与多数节点失联,写入会暂停,直到新领导者选举成功。
- 日志压缩:长期运行需要日志压缩(如快照),否则日志会无限增长。
总结
Raft 协议的写入流程是通过领导者协调日志追加、复制和提交完成的。核心是利用 AppendEntries RPC 和多数派确认机制,确保数据一致性和容错性。