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