Files
cs-note/hhs/Redis/07-集群方案/Raft共识算法.md
T

119 lines
6.9 KiB
Markdown
Raw Normal View History

2026-05-27 11:24:47 +08:00
---
tags: [分布式, Raft, 共识算法, 选举, 故障转移]
create time: 2026-05-27 10:00
---
# Raft 共识算法
## 概述
Raft 是一种分布式**共识算法**,用于让多个节点就某个值达成一致。它的设计目标是比 Paxos 更容易理解和实现。Redis Cluster 的故障转移(Failover)借鉴了 Raft 的**领导者选举**机制来选出新的 Master。
> [!NOTE] "Raft 式投票"不等于完整 Raft
> Redis Cluster 只使用了 Raft 的**选举(Leader Election)** 部分,并没有使用完整的 Raft 日志复制机制。Redis 的数据复制走的是自身的异步复制协议。所以文档中说的是"Raft **式**投票"。
## 一、为什么需要共识?
在分布式系统中,当一个节点宕机时,其他节点需要达成一致:"这个节点确实挂了",并且"由谁来接替它"。如果各节点自行判断、自行行动,就会出现混乱——比如两个节点同时认为自己应该接管服务。
> [!QUESTION] 直觉思考
> 一个 3 人小组的组长突然失踪了。剩下的两个人可以投票选出新组长。但如果 6 人小组刚好分成两个 3 人小组,两组各自选出了一个新组长——怎么办?这就是共识算法要解决的核心问题:**在有故障和网络分区的情况下,如何让大多数节点达成一致决定?**
## 二、Raft 选举核心机制
Raft 将节点分为三种角色:
- **Leader(领导者)**:负责处理所有写请求,向 Follower 复制日志
- **Follower(追随者)**:被动接收 Leader 的心跳和日志
- **Candidate(候选人)**:Follower 超时未收到心跳后,临时切换的角色,负责发起选举请求。如果赢得多数票则晋升为 Leader,失败则退回 Follower
> [!NOTE] 角色转换
> 所有节点初始都是 Follower。正常运行时只有 Follower → Candidate → Leader 这条路径。集群稳定后 Leader 持续发送心跳,Follower 保持被动,不会发起选举。
当 Follower 在一段时间内没有收到 Leader 的心跳,就会发起选举:
```mermaid
sequenceDiagram
participant F1 as Follower 1
participant F2 as Follower 2
participant F3 as Follower 3
Note over F1,F3: Leader 宕机,Follower 超时
F1->>F1: 超时,升级为 Candidate
F1->>F2: RequestVote (我的 term=2, 日志最新)
F1->>F3: RequestVote (我的 term=2, 日志最新)
F2->>F1: VoteGranted (同意)
F3->>F1: VoteGranted (同意)
Note over F1: 获得多数票<br/>成为新 Leader
F1->>F2: AppendEntries (心跳, 我是 Leader)
F1->>F3: AppendEntries (心跳, 我是 Leader)
```
### 关键概念
> [!QUESTION] 如果两个 Follower 同时超时会怎样?
> 它们会同时变成 Candidate 并各自向对方拉票——谁都拿不到多数票,选举失败。Raft 的解决办法是**随机化选举超时**:每个节点的超时时间在 `[T, 2T]` 区间内随机选取(T 通常为 150~300ms)。这样第一个超时的节点大概率会率先完成选举,避免了"选票被瓜分"的僵局。
| 概念 | 含义 | Redis Cluster 中的对应 |
|------|------|---------------------|
| **Term(任期)** | 逻辑时钟,每次选举递增 | **Epoch(集群纪元)** |
| **Election Timeout(选举超时)** | Follower 等待心跳的超时时间,超时则发起选举 | `cluster-node-timeout`(默认 15s) |
| **Heartbeat(心跳)** | Leader 定期向 Follower 发送 AppendEntries RPC 维持地位 | Master → Replica 的 PING/PONG |
| **RequestVote** | Candidate 请求投票的 RPC | Replica 向 Master 发起投票请求 |
| **多数派(Quorum)** | 超过半数节点同意 | 多数 Master 节点投票 |
| **先到先得** | 每个节点在一个 term 内只能投一票 | 同一个 epoch 内,每个 Master 只投一票 |
| **随机化超时** | 每个节点的选举超时设置为一个随机值,避免同时发起选举 | Replica 选举延迟与复制偏移量挂钩 |
> [!TIP] 为什么需要"多数派"?
> 在 N 个节点中,任何两个"多数派"一定有交集。这意味着不可能同时存在两个都获得了多数票的 Candidate——**保证了最多只有一个 Leader**。这是 Raft 正确性的基石。
>
> 数学上:如果 N=3,多数派是 2;如果 N=5,多数派是 3。这也解释了为什么 Redis Cluster 建议使用**奇数个 Master**——4 个 Master 的多数派是 3,只能容忍 1 个故障(和 3 个 Master 的容错能力完全相同),但多了一个节点的开销;而 5 个 Master 能容忍 2 个故障,容错能力真正提升了。
## 三、Redis Cluster 的 Failover 选举
Redis Cluster 将 Raft 选举简化应用到故障转移场景:
```mermaid
flowchart TD
A["Master 宕机"] --> B["Replica 超时未收到心跳"]
B --> C["Replica 将 epoch + 1"]
C --> D["向所有 Master 请求投票"]
D --> E{"获得多数票?"}
E -->|是| F["提升为新 Master<br/>接管 slot 并广播"]
E -->|否| G["等待随机超时后重试"]
style A fill:#fce4ec,stroke:#e91e63
style F fill:#e8f5e9,stroke:#4caf50
style G fill:#fff3e0,stroke:#ff9800
```
**详细步骤:**
1. **超时检测**:Replica 发现 Master 超过 `cluster-node-timeout` 未响应 PONG
2. **纪元递增**:Replica 将当前 epoch 加 1(类似 Raft 的 term 递增)
3. **发起投票**:向集群中所有 Master 节点发送投票请求
4. **投票规则**:每个 Master 在同一个 epoch 内**只投一票**,先到先得
5. **赢得选举**:获得多数 Master 投票的 Replica 升级为 Master
6. **广播新配置**:新 Master 接管旧 Master 的所有 slot,通知集群所有节点
> [!WARNING] 选举失败怎么办?
> 如果多个 Replica 同时发起选举且票数被瓜分,谁都拿不到多数票——选举失败。Redis Cluster 的解决方案是**随机退避**:每个 Replica 等待一个随机时间后再发起下一轮选举,减少再次冲突的概率。
>
> **Redis 的巧妙设计**:这个随机延迟并非完全随机,而是与 Replica 的**复制偏移量(replication offset)** 挂钩——数据越新的 Replica,等待时间越短(`DELAY_FACTOR * 固定延迟 + 随机抖动`)。这保证了**数据最新的 Replica 大概率优先发起选举**,从而最大程度减少数据丢失。
## 四、与 Paxos 的简要对比
| 特性 | Raft | Paxos |
|------|------|-------|
| 可理解性 | 相对简单,角色清晰 | 极其晦涩,号称"只有一个人理解 Paxos" |
| 工程实现 | etcd、Consul、Redis(部分) | Google Chubby、Spanner |
| 适用场景 | Leader 选举 + 日志复制 | 任意值的共识 |
| 活跃节点要求 | 需要多数派存活 | 需要多数派存活 |
## 关联笔记
- [[hhs/Redis/07-集群方案]] — Redis Cluster 集群方案
- [[hhs/Redis/07-集群方案/脑裂问题]] — 当出现两个 Leader 时怎么办
- [[hhs/Redis/07-集群方案/Gossip协议]] — 节点间如何互相发现和交换状态
- [[hhs/Redis/07-集群方案/一致性哈希]] — 数据分片的基础理论