文章目录
我们开始深入解析分布式系统中至关重要的一致性协议—— Raft 协议(Replicated and Fault-Tolerant)。
这是一套被广泛应用于一致性集群的算法(如:Etcd、Consul、TiKV、RocksDB、Zookeeper 替代品),具备易于理解、易于实现、高一致性保证等特点。
第一部分:Raft 协议概述
什么是 Raft?
Raft 是一种为了解决分布式一致性问题而设计的共识算法,属于 Paxos 协议的工程实现替代方案。
目标:在 多个节点之间复制日志,确保状态机数据一致性,容忍部分节点宕机。
应用场景:
- 主从同步数据库(如 Etcd、TiKV)
- 配置中心(如 Consul、Nacos 选主)
- 分布式锁、高可用服务选主
核心特性:
特性 | 说明 |
---|---|
高可用 | 少数节点失效也可继续服务(多数派存活) |
强一致性 | 所有节点状态完全一致,不允许脑裂 |
选主机制 | 所有写请求通过 Leader 完成 |
第二部分:Raft 的三大组成模块
Raft 的核心可拆解为 三个重要子机制,便于理解:
Raft = 选主机制(Leader Election)
+ 日志复制(Log Replication)
+ 安全提交(Log Commitment)
我们逐个来分析:
1. Leader Election(选主机制)
Raft 集群在任一时刻只能有一个 Leader,所有写请求只能通过 Leader 处理。
节点角色:
- Leader:唯一写入者,负责日志复制
- Follower:响应 Leader 的指令
- Candidate:发起选举的节点
选举流程(定时器驱动):
- Follower 若长时间未收到 Leader 心跳 → 转为 Candidate;
- Candidate 向其他节点发送
RequestVote
请求; - 若获得多数节点投票 → 成为 Leader;
- Leader 开始发送心跳包(
AppendEntries
)稳定地位。
防止冲突机制:
- 每个节点维护
term
任期号; - 投票记录:每个节点每 term 只投一次票;
- 如果有两个 Candidate 同时选举,可能出现分裂,最终都会超时重新发起选举;
- 随机化选举超时时间避免多个节点同时成为 Candidate。
示意图(简化):
时间线 →
+---------+---------------------+-----------------+
| Follower| Candidate | Leader |
| T1 | 请求投票 | 发送心跳 |
+---------+---------------------+-----------------+
到此为止,我们完成了 Raft 的第一个核心部分【选主机制 Leader Election】的分析。
接下来进入 Raft 协议的第二核心机制——日志复制机制(Log Replication),并继续结合现实类比 + 源码行为模拟 + 架构角度解释,让你不仅知道“它做了什么”,更知道“为什么这样做”。
第二部分:日志复制机制(Log Replication)
一、场景类比:Leader 是抄笔记的人,Follower 跟着一起抄
类比设定:
- 班长(Leader)负责记课堂笔记;
- 每个同学(Follower)需要保持和班长完全一致的笔记内容;
- 每次班长记下一句话(一次日志),就要发给所有人;
- 只有当大多数人抄完这一句,班长才把它“盖章”视为正式记入笔记本。
二、Raft 中日志的概念:
- 所有客户端请求(写操作)会被封装成一个“日志项”(Log Entry);
- Leader 接收后将该 Log Entry:
- 追加到自己的日志数组末尾
- 并并发地复制给所有 Follower(AppendEntries RPC)
- Leader 会等待大多数节点成功“复制”这条日志,才认为这条日志是“已提交(committed)”。
三、核心字段(LogEntry)结构
class LogEntry {
int term; // 哪一任期添加的日志
int index; // 全局索引编号
String command; // 客户端指令
}
日志本质上就是一串有序的命令序列,最终将送入“状态机”执行。
四、日志复制流程图(简化):
客户端 --> Leader --> 日志追加 --> 向各Follower广播 AppendEntries
Follower --> 校验前一条日志匹配
--> 复制成功 → 返回ACK
Leader收集半数以上ACK → commitIndex 更新 → 应用日志
五、关键机制详解:
✅ 1. 前一条日志校验机制(PrevLogIndex + PrevLogTerm)
-
防止日志冲突(如旧 Leader 写的日志在新 Leader 任期内失效);
-
Leader 发日志时会附带前一条日志的索引和任期:
“我现在要发第10条,这条的前一条是:index=9,term=3,你那里也有这条吗?”
-
Follower 检查是否一致,不一致 → 回退并删除冲突部分,直到对齐。
✅ 2. 日志复制成功判定(大多数)
- 一条日志被**多数节点成功追加(复制)**才会视为成功;
- Leader 更新
commitIndex
,然后通过Apply()
应用到状态机(业务执行); - 避免了 Leader 自己写完就认定“成功”导致数据不一致。
✅ 3. Follower 是被动复制日志
- Follower 不会主动写日志;
- 只能等待 Leader 发来的 AppendEntries;
- 遇到缺失/冲突日志 → 按 Leader 修正,保持一致。
六、现实类比强化理解
Raft 行为 | 现实类比 |
---|---|
AppendEntries | 班长发新笔记句子 |
term 不一致 | 同学之前写错了内容,需要撕掉重写 |
commitIndex | 班长打勾“这句大家都写完了” |
apply | 正式将笔记记入成绩系统 |
七、设计创意与合理性来源
1. 为什么不让每个人自己写日志?
- 分布式中,每个人都自己写,可能写法不同、顺序不同 → 状态不一致;
- 强制“Leader 写,Follower 抄” → 保证顺序和内容一致,状态机安全。
2. 为什么必须多数写入后才 commit?
- 多数节点拥有最新日志,即使 Leader 挂了,新 Leader 也能完整还原状态;
- 少数节点落后也没关系,但不能“少数确认” → 否则可能丢数据。
3. 为什么 Leader 不直接 apply 日志?
- 如果 Leader 自己 apply,Follower 还没接到 → 出现不一致;
- 所以 Raft 明确:日志写入 ≠ 提交;必须 commitIndex >= n 才能 apply。
总结这一部分重点:
点 | 说明 |
---|---|
Log 复制逻辑 | Leader 发 AppendEntries 携带日志项 + 上一日志 |
一致性保障 | 复制给多数后才能提交(强一致) |
冲突处理 | Follower 与 Leader 日志冲突时回退修正 |
安全设计 | 日志未 commit 不允许 apply(状态机安全) |
第三部分:日志提交机制(Log Commit & Apply)
一、什么叫日志提交(Commit)?
在 Raft 中,一条日志即使已经写入所有节点的磁盘,也不能立即认为“完成”,只有满足特定条件的日志,才能称为“已提交(committed)”。
二、Commit 的意义?
- Committed = 可被状态机执行的日志
- 未提交的日志不能触发业务逻辑、改变状态;
- 是 Raft 中**“日志复制 → 日志提交 → 状态执行”**这条流水线的中间关卡。
三、Raft 的提交规则(核心点)
Leader 提交日志的条件是:
某条日志,已经在“当前任期(currentTerm)”内,并且被“多数节点确认追加”**,才能 commit。
为什么要限制“当前任期”?
这是 Raft 的“安全限制设计”,防止新旧 Leader 写的日志相互覆盖。
场景举例:
- 老 Leader 在 term 2 写了日志 A;
- 还没来得及同步到大多数节点时宕机了;
- term 3 出现新 Leader,写了日志 B;
- 如果 term 2 的 A 自动提交 → 数据冲突风险!
所以 Raft 明确:只能提交当前任期成功复制的日志项!
四、commitIndex 和 lastApplied 指针(两个关键指针)
指针 | 作用 |
---|---|
commitIndex | Leader 当前已提交日志的最大 index |
lastApplied | 每个节点(包括 Leader)已执行日志到状态机的最大 index |
日志提交流程如下:
日志复制成功 → Leader 更新 commitIndex → 通知所有 Follower
→ 各自将 lastApplied 推进 → 执行 apply 到状态机
五、状态机执行 apply 的作用?
状态机是每个节点的“业务引擎”,执行日志对应的命令,例如:
- 创建订单
- 更新库存
- 写入配置变更
只有 apply 之后,业务才真正“发生”。
为什么需要 lastApplied?
- 防止节点“误执行”未被提交的日志;
- 落后的节点启动后可“对比”commitIndex 与 lastApplied → 进行 apply 补偿。
六、现实生活类比理解:
Raft 元素 | 班级类比 |
---|---|
Log 复制 | 班长把新笔记分发给所有人 |
Commit 条件 | 超过一半同学抄完了,而且是班长任期内的笔记 |
Apply 到状态机 | 所有人将这句笔记写进作业本中,老师收作业检查 |
lastApplied | 每个人写到第几题了? |
七、设计的合理性(为什么这么设计才最安全?)
✅ 强一致性的保障策略:
场景 | Raft 的保护措施 |
---|---|
日志未同步完就提交 → 风险 | 限制多数派复制 + 任期校验 |
老 Leader 写日志 → 新 Leader 接管 | 日志冲突回退,防止乱序 |
状态不一致风险 | commitIndex 严格控制 apply 进度 |
✅ 保证“新 Leader 日志一定是最完整的”:
- 只有大多数节点拥有这条日志,新 Leader 才能再次选上;
- 所以 Raft 提交日志,实质上也在为下一次选举做准备。
八、总结三步流水线:Raft 日志处理完整流程
客户端请求
↓
Leader 将命令转为 LogEntry
↓
日志追加到本地日志数组
↓
AppendEntries 发往各 Follower
↓
大多数响应 OK → commitIndex++
↓
所有节点更新 lastApplied → 状态机 apply 执行
Raft 的一致性核心就在这里:
- 所有节点 日志内容完全一致(通过复制)
- 所有节点 日志执行顺序完全一致(通过 commit + apply)
这就是强一致性 replicated state machine 的本质。
🙋♂️ 欢迎留言讨论,分享你的技术经验与问题!
如果你在阅读过程中有任何问题,或者想了解更多技术细节,请在评论区留言,我会尽快回复。
📌 想要了解更多技术内容?关注我!
- 文章详情:点击这里关注我的CSDN专栏
🎯 关注“将臣三代”,获取更多价值, 回复【面试】获取专属PDF资料下载链接!!
每周更新最新技术文章和免费资源!
🌟 独家资源:
- 经典Java面试题解析
- 技术架构深度拆解
- 系统优化技巧与项目经验分享
- AI 技术探索