Raft 是当今分布式系统中最广泛使用的共识算法之一,以其可理解性著称。本文将从 Leader 选举、日志复制到安全性保证,逐行解读 Raft 论文核心机制,并附上完整的 Go 语言实现。
一、Raft 核心角色与状态机
Raft 集群中的每个节点在任意时刻处于三种状态之一:Leader(领导者)、Follower(跟随者)或 Candidate(候选者)。正常情况下只有一个 Leader,其余都是 Follower。
type NodeState int
const (
Follower NodeState = iota
Candidate
Leader
)
type RaftNode struct {
mu sync.Mutex
state NodeState
currentTerm int
votedFor int
log []LogEntry
commitIndex int
lastApplied int
nextIndex map[int]int
matchIndex map[int]int
}
二、Leader 选举机制
每个节点维护一个随机的选举超时时间(150ms~300ms)。Follower 在超时后转为 Candidate,增加当前任期号,投票给自己,并向其他节点发送 RequestVote RPC。
func (rn *RaftNode) startElection() {
rn.mu.Lock()
rn.state = Candidate
rn.currentTerm++
rn.votedFor = rn.id
votesReceived := 1
rn.mu.Unlock()
for _, peer := range rn.peers {
go func(p Peer) {
reply := p.RequestVote(rn.currentTerm, rn.id,
rn.lastLogIndex(), rn.lastLogTerm())
rn.mu.Lock()
if reply.Term > rn.currentTerm {
rn.becomeFollower(reply.Term)
} else if reply.VoteGranted {
votesReceived++
if votesReceived > len(rn.peers)/2 {
rn.becomeLeader()
}
}
rn.mu.Unlock()
}(peer)
}
}
三、日志复制与一致性保证
Leader 接收客户端请求后,将命令追加到本地日志,然后通过 AppendEntries RPC 并行复制到所有 Follower。当日志条目被多数节点确认后,Leader 提交该条目并应用到状态机。
func (rn *RaftNode) AppendEntries(args *AppendEntriesArgs) *AppendEntriesReply {
rn.mu.Lock()
defer rn.mu.Unlock()
if args.Term < rn.currentTerm {
return &AppendEntriesReply{Term: rn.currentTerm, Success: false}
}
rn.currentTerm = args.Term
rn.state = Follower
// 日志一致性检查
if args.PrevLogIndex >= 0 {
if args.PrevLogIndex >= len(rn.log) ||
rn.log[args.PrevLogIndex].Term != args.PrevLogTerm {
return &AppendEntriesReply{Term: rn.currentTerm, Success: false}
}
}
// 追加新日志条目,冲突时覆盖
for i, entry := range args.Entries {
idx := args.PrevLogIndex + 1 + i
if idx < len(rn.log) && rn.log[idx].Term != entry.Term {
rn.log = rn.log[:idx]
}
if idx >= len(rn.log) {
rn.log = append(rn.log, entry)
}
}
if args.LeaderCommit > rn.commitIndex {
rn.commitIndex = min(args.LeaderCommit, len(rn.log)-1)
}
return &AppendEntriesReply{Term: rn.currentTerm, Success: true}
}
四、安全性约束
Raft 通过以下机制保证安全性:选举限制(Candidate 的日志必须至少和多数节点一样新)、Leader 只提交当前任期的日志(避免已提交日志被覆盖)、以及严格的日志匹配性质。
五、快照与日志压缩
随着系统运行,日志会不断增长。Raft 通过快照机制压缩日志:将状态机当前状态序列化存储,然后丢弃快照点之前的所有日志条目。
func (rn *RaftNode) takeSnapshot() {
snapshot := rn.stateMachine.Serialize()
snapshotIndex := rn.lastApplied
snapshotTerm := rn.log[snapshotIndex].Term
rn.persistSnapshot(snapshot, snapshotIndex, snapshotTerm)
rn.log = rn.log[snapshotIndex+1:]
}
六、性能优化实践
生产级 Raft 实现通常采用以下优化:Pipeline 批量复制、Leader 并行发送 AppendEntries、异步 Apply 到状态机、以及 Pre-Vote 机制防止网络分区后的无效选举。
完整的 Raft 实现(含测试)可在 GitHub 查看。理解 Raft 是掌握分布式系统一致性的基石,建议读者亲手实现一遍以加深理解。
← 返回首页