← 返回首页

Raft 共识算法深度剖析

分布式系统 2026-10-22 · 阅读约 12 分钟

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 是掌握分布式系统一致性的基石,建议读者亲手实现一遍以加深理解。

← 返回首页