3.2 Raft 的安全读取、成员变更与快照
日志能安全复制,不代表所有读都线性一致,也不代表可以直接改配置文件增删节点。生产 Raft 系统还要处理 Leader 过期、成员集合切换、日志压缩、磁盘和延迟。
Leader 本地读可能过期
一个旧 Leader 被网络隔离后,可能暂时不知道新 Leader 已在更高 term 当选。若它直接读取本地状态并响应,会返回过期结果。
线性一致读常见方案:
- 读请求先通过日志提交,安全但增加写路径开销;
- ReadIndex/多数派确认:证明当前 Leader 仍掌握领导权,并等待状态机应用到安全 index;
- lease read:依赖时钟和租约假设,必须严格满足实现条件。
Follower read 通常是陈旧读,除非先从 Leader 获取安全 read index 并追赶。API 应明确读级别,不能把任意节点 GET 宣称强一致。
Leader 完整性不等于 Leader 永不返回旧数据
Raft 的日志安全保证已提交条目不会被不同值覆盖。客户端可见的线性一致性还需要:
- 每个命令有唯一请求 ID,Leader 切换后不会重复应用;
- 读使用安全协议;
- 状态机按提交顺序且确定性执行;
- 快照与日志恢复保持相同状态;
- 客户端不绕过集群访问单个副本。
成员变更不能直接替换名单
从旧配置 {A,B,C} 直接切到新配置 {C,D,E},两个互不相交的多数派可能分别选出 Leader。
Raft 论文使用 joint consensus:过渡阶段的决策同时需要旧配置和新配置的多数派,之后再提交纯新配置。具体系统也可能使用一次增删一个成员等变体,但必须遵守其协议和操作顺序。
成员变更前检查:
- 新节点是否追赶到足够接近日志尾部;
- 当前是否已有故障成员;
- 变更期间还剩多少 quorum 余量;
- 节点身份是否稳定,旧数据目录会不会以错误身份加入;
- 自动化是否会并发执行多个配置变更。
Snapshot 压缩已提交前缀
日志无限增长会拖慢重放并占满磁盘。节点可以把已应用状态和最后包含的 index/term 保存为快照,再删除此前日志。
落后 Follower 若所需日志已被压缩,Leader 通过 InstallSnapshot 发送快照,再从快照之后继续复制。
快照必须:
- 与某个已应用 index 原子对应;
- 包含恢复状态机需要的全部数据;
- 校验完整性并安全替换旧状态;
- 与并发日志追加协调;
- 在崩溃中不会留下“日志已删、快照未完成”的空洞。
快照不是业务备份。它通常只表示集群内部可恢复状态,不能替代跨区域备份、误删除恢复和历史保留。
磁盘是共识路径的一部分
多数派网络很快但磁盘 fsync 很慢,提交仍会慢。磁盘写满、尾延迟、固件问题和文件系统损坏都可能使节点失去参与 quorum 的能力。
需要观察:
- Leader 变更和无 Leader 时长;
- propose 到 commit、commit 到 apply 的延迟;
- 每个 Follower 的 matchIndex 落后量;
- fsync 延迟、磁盘空间和 WAL 错误;
- 快照生成、传输和恢复耗时;
- 成员变更审计和失败状态。
etcd 等系统的使用边界
Raft 适合复制较小、强一致的控制面状态,不等于把大对象和高吞吐数据流直接塞进 etcd 就能获得免费一致性。需要遵守具体产品的对象大小、存储、压缩、watch 和备份限制。
客户端还应使用产品提供的事务、revision 和 watch 语义,而不是从“底层是 Raft”自行推导 API 保证。
与 Paxos、ZAB 的关系
Paxos 家族和 Raft 都利用多数派交集实现共识,但抽象和工程组织不同;不能把 Raft 的角色、日志规则逐项套在所有 Paxos 实现上。
ZooKeeper 的 ZAB 面向主节点驱动的原子广播和恢复阶段,ZooKeeper 客户端语义还包含会话、watch 和顺序保证。学习一种协议能建立基础,但产品行为必须回到自身规范。
故障测试
- Leader 在本地追加后、复制前崩溃;
- 多数派复制后、客户端收到响应前崩溃;
- 旧 Leader 被隔离,新 Leader 当选后旧 Leader恢复;
- Follower 日志有冲突后缀;
- 快照写到一半进程终止;
- 成员变更中途失去一个节点;
- 磁盘写满或 fsync 长尾。
共识实现的可信度来自模型检查、成熟实现和系统性故障测试,不来自一段简化选举代码。
参考资料
- Ongaro, Ousterhout, Raft dissertation
- etcd, Learning design
- ZooKeeper, Consistency Guarantees