《深入理解 Nacos 集群与 Raft 协议》系列
大家好,我是G探险者!
在前几篇中我们介绍了选主与日志对比机制,它们保证了"谁能成为 Leader"以及"Leader 的日志是否可靠"。
而当 Leader 已选定,系统需要把客户端的写请求写入所有节点的日志中,这个过程就称为 日志复制(Log Replication)。
本篇将讲解:
- Raft 是如何进行日志复制的?
- 如何判断日志是否成功提交?
- 如何保障幂等与一致性?
一、基本流程:写入 Leader,然后复制给 Follower
在 Raft 中,所有写操作必须提交到 Leader 节点,流程如下:
- 客户端发送写请求给 Leader
- Leader 将写请求封装为日志条目(LogEntry)
- Leader 将该日志追加到本地日志中
- 并并发向所有 Follower 发送 AppendEntries 请求
- Follower 收到后尝试匹配前一条日志(前向一致性)
- 匹配成功后,Follower 追加日志,并回复成功
- Leader 收到超过半数节点确认后 → 标记日志为"已提交"
- Leader 通知 Follower 提交该日志(commit)
- Leader 应用日志到状态机并响应客户端
整个过程看似复杂,但每一步都有其必要性。
二、什么是日志"提交"成功?
Raft 中,一条日志被称为"已提交",必须满足:
该日志由 Leader 追加,且已被超过半数节点确认持久化。
这样做的意义是:
- 即使 Leader 崩溃,仍有过半节点持有该日志
- 下次选主,一定会选出有这条日志的节点为新 Leader(见第 3 篇)
→ 已提交的日志,不会丢。
三、AppendEntries 的核心要素
AppendEntries 请求中包含:
- leaderTerm:当前任期
- prevLogIndex + prevLogTerm:前一日志索引+任期(对齐用)
- entries:本次追加的日志条目(可能为空,仅用于心跳)
- leaderCommit:Leader 当前提交的位置
Follower 会进行对比:
- 若 prevLogIndex 和 prevLogTerm 不一致 → 拒绝请求
- 否则 → 覆盖旧日志,从当前追加新日志
四、如何保证日志幂等?
在网络不稳定情况下,可能出现 AppendEntries 重发、乱序。
Raft 通过"前一条日志"对齐机制来保证幂等性:
- 每次发送都携带 prevLogIndex/prevLogTerm
- 若日志有误,Follower 会拒绝,Leader 自动回退并重发
- 不会重复插入相同日志,也不会错位插入
→ 这是一种 乐观+校验补偿式的强一致写入机制。
五、提交顺序与状态机执行
Raft 要求日志是顺序提交的:
- 日志 index 是严格递增的
- 必须 index=1 提交后才能提交 index=2
Leader 提交一条日志后:
- 会逐步推进 commitIndex
- Leader 会把 commitIndex 推送给所有 Follower
- 每个 Follower 在收到时才会"真正执行"该日志到状态机
→ 所有节点最终状态机执行的日志是一致的、顺序的。
六、断电/重启后如何恢复日志?
因为日志会持久化在本地磁盘上,所以:
- Raft 节点宕机重启时,会从本地恢复日志
- 并向新 Leader 进行日志对齐补偿
Raft 的一致性依赖于 持久化日志+对比同步机制,而非内存
七、日志复制在 Nacos 中的体现
你可以看到类似日志:
text
[RAFT] AppendEntries from leader, prevIndex=5, term=7
[RAFT] Log mismatch, conflict at index=5, localTerm=6
[RAFT] Truncate logs from index=5
[RAFT] Append new log entries...
[RAFT] Advance commitIndex to 8
这些就是日志对齐 + 复制 + 提交的全过程。
总结
Raft 的日志复制机制,是系统强一致性的核心支柱:
- 所有写请求必须经 Leader 统一调度
- 复制必须超过半数成功才算提交
- 提交后才能执行,确保所有节点状态一致
- Follower 校验 + 回退机制保证了幂等性
下一篇,我们将以 Nacos 为例讲解:
💡 如果集群未过半节点存活,为什么整个系统不可用?
敬请期待第 5 篇!