1. 日志结构
- 每个节点维护一个日志,日志由条目组成,每个条目包含:
- 命令:客户端提交的操作。
- 任期号:记录创建该条目的领导者的任期。
- 索引:日志条目在日志中的位置。
- 日志分为已提交(Committed)和未提交(Uncommitted)两部分,只有多数节点复制的日志条目才会被提交。
2. 日志复制流程
领导者负责接收客户端请求并将操作复制到所有跟随者节点,具体步骤如下:
- 接收客户端请求:
- 客户端将命令发送给领导者。
- 领导者将命令追加到自己的日志中,作为一个新的日志条目(包含当前任期号和索引)。
- 发送日志条目:
- 领导者通过AppendEntries RPC消息将日志条目发送给所有跟随者,消息包含:
- 领导者的任期号。
- 前一个日志条目的索引和任期号(用于一致性检查)。
- 新日志条目内容。
- 领导者的当前提交索引(Commit Index)。
- 领导者通过AppendEntries RPC消息将日志条目发送给所有跟随者,消息包含:
- 跟随者处理:
- 跟随者接收到AppendEntries消息后:
- 检查消息中的任期号:如果领导者的任期号小于自己的任期号,拒绝请求。
- 检查一致性:验证前一个日志条目的索引和任期号是否与本地日志匹配。
- 如果一致,跟随者将新条目追加到本地日志,并回复成功。
- 如果不一致,跟随者拒绝请求,并告知领导者不匹配的索引。
- 跟随者接收到AppendEntries消息后:
- 领导者处理响应:
- 如果多数跟随者成功复制了日志条目,领导者将该条目标记为已提交(更新Commit Index)。
- 领导者在后续的AppendEntries消息中通知跟随者已提交的索引。
- 跟随者收到提交索引后,将对应日志条目应用到自己的状态机(State Machine)。
- 心跳机制:
- 领导者定期发送心跳消息(空的AppendEntries消息),用于维持权威并防止跟随者触发新选举。
- 心跳消息也包含提交索引,帮助跟随者更新已提交日志。
3. 一致性检查与日志修复
- 如果跟随者的日志与领导者不一致(例如,由于网络分区或故障),领导者通过以下方式修复:
- 领导者收到拒绝响应后,递减发送的前一个日志索引,重试AppendEntries,直到找到跟随者日志的匹配点。
- 找到匹配点后,领导者将后续日志条目发送给跟随者,覆盖不一致的部分。
- 这一过程确保跟随者的日志最终与领导者一致。
4. 提交与应用
- 只有当日志条目被复制到多数节点后,领导者才将其标记为已提交。
- 已提交的日志条目会被领导者和跟随者应用到状态机,确保所有节点的状态一致。
- RAFT保证已提交的日志不会被回滚(Leader Completeness属性)。
5. 异常处理
- 领导者故障:如果领导者崩溃,跟随者会在选举超时后触发新选举。新领导者当选后,继续日志复制,可能覆盖旧领导者的未提交日志。
- 网络分区:如果跟随者与领导者失联,领导者继续尝试发送AppendEntries。一旦重新连接,跟随者的日志会通过一致性检查与领导者同步。
- 日志冲突:新领导者当选后,可能发现某些节点的日志包含未提交的条目。通过日志复制,新领导者会强制覆盖这些不一致的日志。
6. 优化
- 批量复制:领导者通常一次发送多个日志条目,提高效率。
- 流水线复制:领导者可以在收到确认前继续发送后续日志条目,减少延迟。
- 快照(Snapshot):当日志过长时,节点会创建状态机快照,删除旧日志,降低存储和传输开销。