7.4 KiB
tags, create time
| tags | 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 协议的核心思想可以归结为三步,每一步都体现了"少而精"的通信策略:
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,传播方式如下:
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 不会因为一次超时就判定节点下线,而是采用两阶段确认,避免误判:
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 去中心化完成