存储系统为什么常用 Raft 管理副本?
简化版
Raft 用 Leader 对写入排序、复制日志并经多数派提交,使一个分片的多个副本在崩溃和网络分区下仍保持单一已提交历史。它适合管理强一致分片副本,但不能自动解决跨分片事务、存储引擎落盘、线性一致读、成员变更和热点问题,这些仍需完整工程实现。
详细版
一个分片通常对应一个 Raft Group。Leader 接收写请求并生成带 term/index 的日志,通过 AppendEntries 复制;当前任期日志被多数副本持久化后推进 commitIndex,各副本按同一顺序应用到 RocksDB、B+Tree 等本地状态机。
多数派交集、投票的日志新旧限制和日志匹配规则共同保证已提交日志不会被未来 Leader 覆盖。少数派失联时不能继续提交,因此网络分区下以可用性换安全性。
Raft 只决定命令顺序,不替代本地存储引擎。系统还要定义 WAL 与 Raft Log 的持久化边界、apply 是否幂等、快照如何原子安装、Leader 读如何做 ReadIndex,以及请求超时后的客户端去重。
多分片系统会运行许多 Raft Group,必须调度 Leader 和副本、限制恢复流量,并处理跨分片事务。把“用了 Raft”直接等同于“数据库所有操作都强一致”是不准确的。
完整版教学
一、存储副本需要解决什么
复制不是简单把字节拷贝三份。并发写入必须有唯一顺序,Leader 故障后新 Leader 不能丢掉已确认写,落后副本需要找到共同前缀并追赶。Raft 把这些问题收敛成复制状态机:先对日志顺序达成共识,再确定性应用命令。
Client -> shard leader -> Raft log -> majority durable -> commit -> apply engine
二、一个写请求如何提交
假设三副本 A、B、C,A 是 Leader。A 把写入追加到 index=120、term=8,本地持久化后并行复制;B 持久化成功而 C 超时,A 与 B 已构成 2/3 多数。若该条目属于当前 term,A 可推进 commitIndex,再应用并回复客户端。
| 时刻 | A | B | C | 是否可提交 |
|---|---|---|---|---|
| t1 | 有 120 | 无 | 无 | 否 |
| t2 | 有 120 | 有 120 | 超时 | 是,2/3 多数 |
| t3 | 已应用 | 已提交待应用 | 后续追赶 | 已提交事实不变 |
三、为什么多数派保护历史
提交多数派与未来选举多数派必有交集。候选人还必须满足日志至少不比投票者旧,因而缺少已提交历史的节点无法合法当选。AppendEntries 的 prevLogIndex/prevLogTerm 检查确保副本只在共同前缀后追加。
旧 Leader 在少数派里可能暂时仍自称 Leader,但拿不到多数 ACK,不能形成新提交。安全性保证的是不会有两条冲突日志都合法提交,并不保证每个节点瞬间角色一致。
四、Raft Log 与本地引擎的边界
Raft Log 是共识输入顺序,本地引擎保存应用后的状态。commitIndex 表示已经形成共识,appliedIndex 表示状态机已经执行到哪里。线性一致读必须等 appliedIndex 达到读屏障,否则即使 Leader 身份安全,也可能返回旧状态。
崩溃恢复时,节点重放已提交但未应用日志。状态机应用最好以日志 index 或业务请求号保证幂等,避免“引擎已写成功、appliedIndex 尚未持久化”导致重复副作用。
五、快照和日志压缩
Raft Log 不能无限增长。节点可在已应用边界创建快照,记录 lastIncludedIndex/Term,校验后原子发布并截断更早日志。严重落后的 Follower 若所需日志已删除,就通过 InstallSnapshot 恢复。
快照只能覆盖已提交且已应用的前缀,不能把未提交尾部固化。安装时也不能让半份快照成为活动状态;应写临时文件、校验 checksum,再原子切换元数据。
六、多 Raft Group 的工程代价
数据库常把每个 range/shard 建成独立 Group。这样故障和写入可以分散,但数万个 Group 会带来心跳、定时器、日志文件和调度开销。热点分片的 Leader 还可能集中在同一节点,需要持续做 Leader balance 和 replica balance。
跨分片原子事务不由单个 Raft Group 解决,还需要 2PC、时间戳/锁或确定性事务协议。恢复时应限制 snapshot 和日志追赶带宽,否则副本自愈会抢占前台 I/O,造成级联故障。
记忆钩子:Raft 负责“这一分片的命令以什么顺序成为事实”,存储引擎负责“事实如何落盘和查询”;两层都正确才是数据库正确。
七、常见误区与追问
- 误区:多数复制的任何旧任期日志都能直接提交。 Raft Leader 只能据当前任期的多数复制推进提交,旧前缀随之间接提交。
- 误区:用了 Raft,所有读天然线性一致。 还要确认 Leader 身份、建立读屏障并等待状态机应用。
- 误区:Raft 替代本地 WAL。 两者边界由实现决定,但持久化和崩溃恢复不能省略。
- 追问:客户端超时能否直接重试? 可以重试,但必须携带请求 ID 去重,因为原请求可能已提交。
- 追问:少数派能否提供写服务? 不能安全提交;是否提供陈旧读取决于明确的一致性接口。
- 追问:跨分片事务怎么办? 需要额外事务协调,单个 Group 的共识不能覆盖多个日志组。
八、加强记忆
回答按“排序、复制、提交、应用、恢复”串起来:Leader 排序,多数派提交,状态机按序应用,快照帮助落后副本恢复。再主动说出 current-term 提交、commitIndex/appliedIndex、读屏障和跨分片边界,才能说明你理解的是存储系统里的 Raft,而不是只背选举流程。