Files
cs-note/hhs/Redis/07-集群方案/Gossip协议.md
T
2026-05-27 11:24:47 +08:00

151 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [分布式, Gossip, 最终一致性, 去中心化, Redis Cluster]
create time: 2026-05-27 10:00
---
# Gossip 协议
## 概述
Gossip 协议(也叫 Epidemic Protocol,流行病协议)是一种**去中心化的信息传播协议**。它的灵感来自社交网络中的"八卦传播"——一个人把消息告诉几个朋友,每个朋友再告诉他们各自的几个朋友,信息很快就会传遍整个社交圈。
Redis Cluster 使用 Gossip 协议在节点之间同步集群状态(谁是 Master、哪些 slot 归谁、节点是否健康),而不需要一个中心化的协调者。
## 一、为什么需要 Gossip?
分布式集群面临一个基本问题:**每个节点如何知道其他节点的状态?**
两种经典方案:
| 方案 | 做法 | 优点 | 缺点 |
|------|------|------|------|
| **中心化注册中心** | 引入 ZooKeeper/etcd 管理节点信息 | 强一致,查询快 | 单点故障风险,增加运维复杂度 |
| **去中心化 Gossip** | 节点之间互相"八卦",没有中心 | 无单点故障,天然高可用 | 最终一致性,传播有延迟 |
Redis Cluster 选择了后者——Redis 本身就是一个高性能的单线程数据库,引入外部依赖(ZK/etcd)会增加架构复杂度,违背了 Redis 轻量、快速的设计哲学。
## 二、Gossip 的传播原理
Gossip 协议的核心思想可以归结为三步,每一步都体现了"少而精"的通信策略:
```mermaid
flowchart LR
A["节点 A<br/>有新消息"] -->|"1. 随机选择几个邻居"| B["节点 B, C, D"]
B -->|"2. 收到后继续转发"| E["节点 E, F"]
C -->|"2. 收到后继续转发"| G["节点 G, H"]
E -->|"3. 持续传播直到收敛"| I["所有节点"]
```
具体到 Redis Cluster,传播方式如下:
```mermaid
sequenceDiagram
participant A as Node A
participant B as Node B
participant C as Node C
participant D as Node D
Note over A: 新状态: slot 0~5460 归我管
A->>B: PING (携带 A 的状态摘要 + 随机挑 3 个节点状态)
B->>A: PONG (携带 B 的状态摘要)
A->>C: PING (携带更新后的状态)
C->>A: PONG
Note over A,B,C,D: 几轮之后<br/>所有节点都知道 slot 0~5460 归 A 管
```
### 收敛速度
Gossip 的收敛速度与集群规模的关系:
- **传播轮数**:O(log N) 轮后信息传遍所有节点
- **每轮通信量**:每个节点选 min(3, sqrt(N)) 个目标
| 集群规模 | 每节点每轮通信目标数 | 大约收敛轮数 | 总消息量 |
|---------|-------------------|------------|---------|
| 6 节点 | 3 | ~3 轮 | ~18 条 |
| 100 节点 | 10 | ~7 轮 | ~700 条 |
| 1000 节点 | 32 | ~10 轮 | ~10000 条 |
> [!NOTE] Redis Cluster 的 Gossip 通信频率
> 每秒执行 10 次(即每 100ms 一次),每次从集群中随机选取 5 个节点,从中选出最久没通信的一个节点发送 PING。这个频率在 `cluster-node-timeout` 的窗口内足以让状态收敛。
> [!QUESTION] 思考
> 为什么不直接让每个节点把消息广播给所有其他节点?对比一下"全广播"和"Gossip"的带宽开销。
## 三、Gossip 的四种消息类型
Redis Cluster 的 Gossip 通信使用以下四种消息(实际协议中有更多,这四种是核心):
| 消息类型 | 发送条件 | 作用 |
|---------|---------|------|
| **PING** | 每秒定时发送 | 心跳探活 + 携带发送者自身状态和随机选中的节点状态 |
| **PONG** | 收到 PING 后回复 | 确认存活 + 携带接收者自身状态 |
| **MEET** | 新节点加入集群 | 强制目标节点加入集群(区别于 PING 的"可选") |
| **FAIL** | 检测到某节点不可达 | 广播故障通知,触发 Failover |
> [!NOTE] PING 报文不只是心跳
> PING 报文中携带的信息远不止"我还活着"。它包含:
> - 发送节点的 nodeId、IP、端口、角色(Master/Slave)
> - 集群当前的 **clusterEpoch**(配置版本号,用于冲突解决)
> - 发送节点负责的**槽位 bitmap**(一个 16384 位的位图)
> - 发送节点的 **configEpoch**(本节点见过的最大纪元)
> - 随机选择的其他节点的状态(间接传播,每个节点附带 2~3 字节 flags 表示 PFAIL 等状态)
>
> 这意味着,即使节点 A 从未和节点 D 直接通信,A 也能通过 B 的 PING 报文了解到 D 的状态——这就是"八卦"的力量。同时,epoch 机制保证了:当两个节点对 slot 归属有分歧时,epoch 更大的配置胜出,解决了最终一致性的冲突问题。
## 四、故障检测:PFAIL → FAIL 双阶段确认
Gossip 的一个关键实战应用是故障检测。Redis Cluster 不会因为一次超时就判定节点下线,而是采用**两阶段确认**,避免误判:
```mermaid
sequenceDiagram
participant A as Node A
participant B as Node B
participant C as Node C
participant D as Node D
A->>A: 与 Node D 通信超时
Note over A: 标记 D 为 PFAIL<br/>(可能下线, 暂不处理)
A->>B: PING (携带: D is PFAIL)
A->>C: PING (携带: D is PFAIL)
B->>A: PONG (我这里 D 也超时了, PFAIL)
C->>A: PONG (D 对我正常, 无 PFAIL)
Note over A: 收集到集群半数以上<br/>Master 对 D 的 PFAIL 票
Note over A: D 从 PFAIL 升级为 FAIL<br/>广播 FAIL 消息
A->>B: FAIL(D)
A->>C: FAIL(D)
Note over B,C: 收到 FAIL 后<br/>触发 D 的 Slave 故障转移
```
> [!IMPORTANT] 为什么要两阶段?
> **PFAIL(Possibly Fail)** 是本地判断:节点 A 发现 D 通信超时,可能只是 A 和 D 之间网络抖动。
> **FAIL** 是集群共识:只有当集群中**过半 Master 节点**都在 `cluster-node-timeout` 窗口内标记 D 为 PFAIL,才会升级为 FAIL。
>
> 这个设计本质上是一个轻量级的"多数派投票"——和 Raft 中的选举逻辑异曲同工,但通过 Gossip 而非显式投票完成。
## 五、Gossip 的局限性
Gossip 并非完美方案,它有一个核心 trade-off:
> [!IMPORTANT] 最终一致性,而非强一致性
> Gossip 保证信息**最终**会传遍所有节点,但不保证**立刻**。在传播过程中,不同节点对集群状态的认知可能是不一致的。
>
> 这就是为什么 Redis Cluster 需要 **epoch(集群纪元)** 来解决冲突——当两个节点对 slot 归属有分歧时,epoch 更大的配置胜出。
其他局限:
| 局限 | 说明 | Redis 的应对 |
|------|------|------------|
| 消息冗余 | 同一条信息可能被多次传播 | 报文中携带 epoch,节点收到旧 epoch 的消息直接丢弃 |
| 不适合实时场景 | 收敛需要数秒 | 故障检测用 PFAIL/FAIL 双阶段确认,不依赖 Gossip 收敛 |
| 带宽开销随节点增长 | O(N log N) 级,每节点每轮 min(3, √N) 个目标 | 大集群通过调低通信频率、使用 proxy 分层缓解 |
> [!QUESTION] 对比思考
> Gossip 和 Raft 都能解决分布式一致性问题,但适用场景不同。前者是"最终一致",后者是"强一致"。结合 [[hhs/Redis/07-集群方案/Raft共识算法]],思考:Redis Cluster 为什么同时用了这两种协议——Gossip 负责什么?Raft 负责什么?
## 关联笔记
- [[hhs/Redis/07-集群方案]] — Redis Cluster 集群方案总览
- [[hhs/Redis/07-集群方案/Raft共识算法]] — 对比理解:Gossip 是最终一致,Raft 是强一致;故障转移中 Slave 选举使用类 Raft 投票
- [[hhs/Redis/06-主从复制/哨兵模式]] — 对比理解:哨兵模式也做故障检测,但由哨兵节点集中负责,而 Cluster 用 Gossip 去中心化完成