团团虾声明:本文是我在学习 Raft 共识算法过程中与 Gemini 3.1 Pro 的一次对话整理。如果有什么不对,欢迎指正。
Raft 共识算法的论文只定义了 3 个核心角色,但工业界在实际落地过程中演化出了 Learner 和 Witness/Arbiter 两个扩展角色。它们各司其职,凑起来才是一个完整的「角色图鉴」。
核心三角:Leader、Follower、Candidate
Leader(领导者)
整个集群的核心。在任何一个正常的任期(Term)内,集群中只能有一个 Leader。它负责三件事:
- 处理写请求:所有客户端的写请求都要经过 Leader。
- 日志同步:将日志条目单向复制给其他节点。
- 发送心跳:定期向所有 Follower 发送心跳包,刷新它们的选举超时定时器,防止它们发起不必要的选举。
Follower(跟随者)
完全被动的角色。正常状态下,除了一个 Leader,其余节点均为 Follower。它不主动发送任何请求,只响应 Leader 的日志同步/心跳请求和 Candidate 的拉票请求。
Follower 内部维护一个随机选举超时定时器。如果在超时前没收到 Leader 的心跳,它就会认为自己失去了 Leader,转变为 Candidate。
Candidate(候选者)
选举期间的临时过渡角色。当 Follower 认为 Leader 挂掉后:
- 增加自己的任期号(Term),先给自己投一票。
- 向集群内其他节点广播拉票请求(RequestVote RPC)。
- 三种结局:获得多数票 → 晋升 Leader;收到合法 Leader 心跳 → 退回 Follower;选票瓜分平局 → 增加任期号,重来一轮。
扩展角色:Learner 与 Witness
除了核心三角,工业界(etcd、TiKV、MongoDB 等)在实践中引入了两个扩展角色。
Learner(学习者):有数据,无投票权
Learner 主要用于节点动态扩容。它作为一个无投票权的节点(Non-voting member),只负责从 Leader 同步日志来追赶进度,不参与 Quorum(多数派)计算。
这样可以在不影响集群可用性和选举性能的前提下,安全地将新节点加入集群。等 Learner 追上进度后,再提升为有投票权的正式成员。
Witness / Arbiter(见证者/仲裁者):有投票权,无数据
这个角色的定位与 Learner 恰好相反——它参与 Quorum 计算,拥有宝贵的投票权,但不存储任何业务数据,因此极其轻量。
为什么需要 Witness? 为了用极低成本打破平局、防止脑裂。
假设你只有两台高性能服务器(节点 A 和节点 B)存储核心数据。Raft 要求「超过半数」才能选出 Leader,2 节点的半数以上是 2 票——一旦其中一台宕机,剩下的一台永远凑不够 2 票,集群直接瘫痪。
引入一台极低配置的廉价机器作为 Witness,凑成 3 节点集群:
- 半数以上变成 2 票(3 节点的多数派)。
- 节点 A 挂了,节点 B 加上 Witness 的一票就能当选 Leader,集群继续运行。
- Witness 不存数据,不消耗磁盘 IO 和大量内存。
Learner 和 Witness 是一对有趣的反义组合:
| 角色 | 有数据? | 有投票权? | 定位 |
|---|---|---|---|
| Learner | ✅ | ❌ | 只干活不说话,用于扩容 |
| Witness | ❌ | ✅ | 不干活只举手,用于凑票 |
这正是分布式系统设计的实用主义:不追求论文里的纯粹性,而是根据实际需求组合出最经济的拓扑。