Raft 算法不会丢数据的核心原因,是通过 严格的日志复制规则、选举安全性约束、提交机制 三重保障,确保只有 “集群共识确认的数据” 才会被提交,且故障恢复后能精准恢复到一致状态。
一、日志复制:数据同步必须过半数确认
Raft 的写操作流程,从根源上杜绝了单机数据丢失的可能:
-
写请求只能走 Leader
所有客户端的写操作,必须先发给 Leader,Follower 不接受写请求。
Leader 收到请求后,会先把操作记录为一条 未提交的日志条目(包含 term 任期号、日志索引)。
-
同步日志到 Follower,必须过半数确认
Leader 会把这条日志同步给所有 Follower,只有当 超过半数节点(包括 Leader 自己)成功复制了这条日志,Leader 才会将这条日志标记为 已提交。
- 数学保障:集群中任意两个 “过半数节点集合” 必然有交集 → 确保日志不会丢失。
- 举例:3 节点集群,至少 2 个节点复制成功才算提交;5 节点集群,至少 3 个节点复制成功。
-
提交后才会响应客户端
只有日志提交成功,Leader 才会执行具体的写操作(比如修改数据),并向客户端返回 “操作成功”。
→ 客户端收到成功响应时,数据已经在集群多数节点上存在了,就算 Leader 宕机,其他节点也有这份数据。
二、选举安全性:只有数据最新的节点才能当 Leader
这是 Raft 不丢数据的 最关键约束 —— 确保新 Leader 一定拥有集群中所有已提交的日志。
选举的核心规则(必须满足 2 个条件)
当 Follower 因收不到 Leader 心跳而变成 Candidate 时,想要当选 Leader,必须满足:
- Candidate 必须获得超过半数节点的投票。
- 投票节点只会把票投给 “日志比自己更新的 Candidate”。
- 日志 “更新” 的判断标准:任期号更大 → 若任期号相同,则 日志索引更长。
为什么这个规则能保证不丢数据?
- 已提交的日志,必然存在于 超过半数节点 上。
- Candidate 想要拿到超过半数的投票,就必须和这些 “持有已提交日志的节点” 的日志保持一致 → 否则拿不到票。
- 最终当选的 Leader,必然包含集群中所有已提交的日志。
反例:旧数据节点不可能当选 Leader
假设集群有 3 个节点(A=Leader, B、C=Follower):
- A 提交了一条日志(已同步到 B),但还没同步到 C 时,A 宕机。
- B 和 C 都会变成 Candidate,但 C 的日志比 B 旧。
- B 向 C 拉票时,C 发现 B 的日志更新 → 投给 B;B 自己也投自己 → 拿到 2 票(过半数),当选 Leader。
- C 会从新 Leader B 同步缺失的那条已提交日志 → 数据完整恢复。
- 若 A 后来重启,会发现 B 的日志更新 → 自动降级为 Follower,并同步 B 的日志 → 最终集群日志一致。
三、故障恢复:Follower 自动追平 Leader 日志
Raft 集群中,节点故障恢复后,会通过 日志一致性检查 自动同步缺失数据,确保集群状态一致:
-
Leader 维护 nextIndex 指针
Leader 会为每个 Follower 维护一个 nextIndex(表示下一次要发送给 Follower 的日志索引)。
-
日志不匹配时,Leader 回退重试
当 Leader 发现 Follower 的日志和自己不一致时,会 回退
nextIndex,从更早的日志开始重新同步,直到找到双方一致的日志点,再把后续的日志全部同步给 Follower。 -
旧 Leader 降级同步
故障的旧 Leader 重启后,会发现集群已有新 Leader,此时会自动降级为 Follower,并从新 Leader 同步最新日志 → 最终所有节点日志完全一致。
四、关键结论:Raft 不丢数据的 3 个承诺
- 已提交的数据绝不丢:客户端收到 “写成功” 响应时,数据已在多数节点存在,必然能被后续 Leader 继承。
- 未提交的数据可丢弃:如果 Leader 没等到多数节点确认就宕机,这条未提交的日志会被丢弃 —— 但客户端不会收到成功响应,业务层面感知不到(相当于写失败)。
- 集群始终一致:故障恢复后,所有节点最终会拥有完全相同的日志,不会出现数据分歧。
五、和 MySQL 主从的对比
很多人会把 Raft 和 MySQL 传统主从对比,核心区别在于 数据一致性保障:
| 特性 | Raft 算法 | MySQL 传统主从(异步复制) |
|---|---|---|
| 写入口 | 只有 Leader 可写 | 主库可写,从库只读 |
| 数据提交条件 | 过半数节点同步成功 | 主库写入成功即返回,从库异步复制 |
| 故障丢数据风险 | 无(已提交数据) | 有(主库宕机时,未同步的从库会丢数据) |
| 新主数据完整性 | 必然包含所有已提交数据 | 可能选到数据较旧的从库(需手动选 GTID 最新的) |
