引言:分布式系统的核心难题
构建分布式系统面临的核心挑战之一是如何在多个节点之间达成一致。当集群中的某个节点发生故障时系统如何继续运行?如何确保所有节点看到相同的数据状态?这就是共识问题(Consensus Problem)。
Raft 共识算法由 Diego Ongaro 和 John Ousterhout 在 2013 年的论文《In Search of an Understandable Consensus Algorithm》中提出。相比 Paxos 的晦涩难懂,Raft 的核心设计目标是可理解性——用分离分解的方式将共识问题拆分为领导者选举、日志复制和安全性保证三个相对独立的子问题,使工程师更容易正确实现。
如今,Raft 已成为工业界分布式协调服务的事实标准。Etcd(Kubernetes 的存储后端)、Consul、TiKV、CockroachDB、ZooKeeper(通过 ZAB 协议类似实现)等核心基础设施均采用 Raft 或其变体。本文将深入剖析 Raft 的完整机制,并给出原汁原味的 Go 实现。
一、状态机复制与角色模型
1.1 状态机复制(State Machine Replication)
Raft 基于状态机复制(SMR)范式。其核心思想是:如果所有节点从相同的初始状态开始,按相同顺序执行相同的操作序列,那么它们最终将到达相同的状态。Raft 中的"操作"被存储在只追加日志(Append-Only Log)中。
每个 Raft 节点运行一个确定性状态机,所有节点从相同初始状态出发,只要保证日志内容和顺序完全一致,就能保证所有节点的状态机最终收敛到相同状态。
1.2 三种角色
Raft 每个节点在任意时刻处于以下三种角色之一:
Leader(领导者):处理所有客户端请求、管理日志复制、定期发送心跳维持领导权。一个 Term 内有且仅有一个 Leader。
Follower(跟随者):被动响应 Leader 的 RPC 请求(心跳、日志追加),不主动发起通信。若超时未收到 Leader 心跳,则转换为 Candidate。
Candidate(候选者):从 Follower 超时转换而来,请求其他节点投票。获得多数票的候选者成为新 Leader。
角色状态转换关系:Follower → Candidate(选举超时)→ Leader(获得多数票)→ Follower(发现更高 Term)
二、领导者选举机制
2.1 Term 任期号
Raft 将时间离散化为Term(任期),每个 Term 是一个连续递增的整数。Term 在 Raft 中充当逻辑时钟的角色:
- 每个节点持久化存储 currentTerm(当前已见过的最高 Term)
- 节点通信时携带 Term,若发现自己的 Term 较小则更新
- 若发现更大的 Term,无论当前角色如何,立即退为 Follower
- RequestVote 和 AppendEntries RPC 中拒绝 Term 过期的请求
2.2 选举过程
Follower 在选举超时时间内未收到 Leader 心跳,则触发选举:
1. 递增 Term:将 currentTerm 加 1,转为 Candidate
2. 自投一票:VoteFor 设为自身 ID
3. 并行拉票:向集群中所有其他节点发送 RequestVote RPC
4. 等待结果: - 获得集群多数(N/2 1)选票 → 成为 Leader - 收到 Term 更高的 AppendEntries → 退为 Follower - 选举超时(平局导致)→ 重新发起选举
2.3 投票安全保证
Raft 的两条核心投票规则保证一个 Term 内至多产生一个 Leader:
规则一:每 Term 每节点一票。每个节点在一个 Term 内最多投一票,先到先得。
规则二:日志至少一样新。Candidate 的日志必须至少和投票者一样新(比较最后一条日志的 Term 和 Index),防止已提交日志被覆盖。
// RequestVote Handler:投票决策逻辑
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
// 1. 如果 args.Term

发表评论 取消回复