Files
autumn-recruitment/03.Redis/core/集群与哨兵机制.md
T

208 lines
7.8 KiB
Markdown
Raw Normal View History

2026-08-08 19:01:04 +08:00
---
tags: [redis, cluster-sentinel, sharding, gossip, failover]
create time: 2026-08-08 18:43
update time: 2026-08-08 18:43
---
# 集群与哨兵机制
## 概述
Redis 单实例架构天然面临内存上限和单点故障问题。Redis Cluster(原生分片集群)解决了水平扩展,Sentinel(哨兵)解决了高可用自动故障转移。理解它们的架构、通信协议和选举机制,是构建生产级 Redis 基础设施的前提。
## 核心原理
### Sentinel — 哨兵架构
哨兵不是对数据进行分片的,而是在主从复制的基础上增加自动化故障检测和恢复能力。
**哨兵节点组成:**
```mermaid
graph TB
subgraph RedisCluster["Redis 主从集群"]
M["Master<br/>192.168.1.10:6379"]
S1["Slave1<br/>192.168.1.11:6379"]
S2["Slave2<br/>192.168.1.12:6379"]
end
subgraph Sentinels["哨兵集群"]
SEN1["Sentinel-1<br/>192.168.1.10:26379"]
SEN2["Sentinel-2<br/>192.168.1.11:26379"]
SEN3["Sentinel-3<br/>192.168.1.12:26379"]
end
subgraph Clients["客户端"]
APP1["应用服务A"]
APP2["应用服务B"]
end
M -->|replication| S1
M -->|replication| S2
SEN1 -->|监控| M
SEN2 -->|监控| M
SEN3 -->|监控| M
SEN1 <-->|Gossip通信| SEN2
SEN2 <-->|Gossip通信| SEN3
SEN3 <-->|Gossip通信| SEN1
APP1 -->|连接| M
APP2 -->|连接| M
```
**四大职责:**
1. **监控(Monitoring)**:定期检查 Master、Slave 和其他 Sentinel 是否可达(通过 PING)。
2. **提醒(Notification)**:当某个节点发现异常时,通知其他 Sentinel。
3. **自动故障转移(Failover)**:当 master 被标记为客观下线(ODOWN)时,选择一个 slave 升为主节点。
4. **配置提供者(Configuration Provider)**:客户端连接 sentinel 来获取当前 master 地址。
**故障转移步骤:**
```mermaid
sequenceDiagram
participant S1 as Sentinel-1
participant Quorum as Quorum(n/2+1)
participant Master as 原Master
participant Slave as Slave节点
S1->>Master: PING (超时判定主观下线)
S1->>S1: 标记为 Subjectively Down (SDOWN)
S1->>Quorum: 询问是否也认为 Master SDOWN
Quorum-->>S1: n/2+1 确认
S1->>S1: 标记为 Objectively Down (ODOWN)
S1->>S1: 选举 leader Sentinel
Note over S1: RAFT-like 选举<br/>先到先得
S1->>Quorum: 投票选出 Failover Owner
S1->>Slave: 选出最合适的 slave (复制偏移量最大)
Slave->>Slave: SLAVEOF NO ONE
Slave->>Slave: 提升为 Master
S1->>其他 Slave: SLAVEOF new-master IP PORT
S1->>Clients: 更新 master 地址
```
**关键参数:**
| 参数 | 默认值 | 含义 |
|------|-------|------|
| down-after-milliseconds | 30000 | 主观下线的判断时间(ms) |
| failover-timeout | 180000 | 故障转移超时(ms) |
| parallel-syncs | 1 | 故障转移后同时同步的新 master 数量 |
> [!NOTE]
> quorum = n/2 + 1 中的 n 是配置的 Sentinel 节点总数,不是存活节点数。如果配置了 3 个 Sentinel,需要至少 2 个认为 master SDOWN 才会触发 ODOWN。这意味着少数派宕机不影响多数派的故障检测。
### Redis Cluster — 槽位分配
Redis Cluster 是无中心架构,每个节点都知道完整的拓扑结构。数据分片基于 **哈希槽(hash slot)**。
**16384 个槽位:**
- Redis Cluster 将 16384 个 hash slot 分布在多个节点上
- Key 的 slot 计算:`CRC16(key) % 16384`
- 客户端可以通过 `ASK` 和 `MOVED` 重定向消息定位到正确节点
**槽位迁移流程(在线迁移):**
```mermaid
sequenceDiagram
participant Admin as 管理员
participant NodeA as Source Node
participant NodeB as Target Node
participant Client as 客户端
Admin->>NodeA: CLUSTER ADDSLOTS 0-5460
NodeA->>NodeB: 开始迁移 slots
Note over NodeA,NodeB: 阶段1: IMPORTING
NodeA->>NodeA: setslot IMPORTING <slot> <target-id>
NodeB->>NodeB: setslot MIGRATING <slot> <source-id>
loop 逐个 key 迁移
NodeA->>NodeB: MIGRATE host port key timeout
Note over NodeA: client 查 key → ASK redirect →<br/>再查到目标 node
end
NodeA->>NodeA: setslot NODE <my-id> (迁移完成)
Client->>NodeB: 直接查找 (no redirect needed)
```
### Gossip 协议
哨兵之间使用简单的主子 Gossip 协议进行信息交换:
- **PUBLISH/SUBSCRIBE**:哨兵可以发布订阅频道获取全局事件
- **HEARTBEAT**:每秒钟互相发送 PING,包含自身的版本号和当前 master 状态
- **INFO 传播**:每个哨兵维护整个集群的视图,定期与其他哨兵同步
- **领导选举**:基于 Raft 思想的简化版——先到先得,获得 quorum 票数成为 owner
> [!WARNING]
> Gossip 协议存在最终一致性延迟。在哨兵刚刚选出新 leader 的瞬间,未收到通知的哨兵可能仍认为旧状态是正确的,此时如果再次发生故障转移请求可能出现混乱。实际生产中要合理设置 timeouts。
### CP vs AP 权衡
Redis Cluster 在设计上做出了明确的取舍:
```mermaid
graph LR
A["CAP 定理"] --> B{"一致性 or 可用性?"}
B -->|Redis Cluster 选择| C["AP 倾向<br/>(可用性优先)"]
B -->|对比方案| D["Redis Sentinel CP 倾向"]
C --> E["分片节点不可用时<br/>部分操作返回 -CLUSTERDOWN"]
D --> F["单主从切换期间<br/>短暂不可用但最终一致"]
E --> G["适合:缓存/排行榜等可接受短暂不一致场景"]
F --> H["适合:会话存储/限流计数等要求强一致场景"]
```
| 维度 | Redis Sentinel | Redis Cluster |
|------|---------------|---------------|
| 一致性模型 | CP(主从切换期间短暂不可用但保证一致) | AP(分片后允许不同分区有不同视角) |
| 数据分片 | 否(全量复制到每个从节点) | 是(16384 槽位分散) |
| 多 DB 支持 | 支持(DB0 ~ DB15) | 不支持(仅 DB0) |
| 扩容方式 | 手动迁移或重新搭建 | 在线增量迁移 |
| 适用场景 | 中小规模、强一致性要求 | 大规模、水平扩展需求 |
## 代码示例
Go 中使用 Redigo 连接 Sentinel:
```go
import "github.com/gomodule/redigo/redis"
sentinelAddrs := []string{"192.168.1.10:26379", "192.168.1.11:26379"}
pool := &redis.Pool{
MaxIdle: 10,
DialContext: func(ctx context.Context) (conn redis.Conn, err error) {
// 自动发现 master,无需硬编码地址
conn, err = redis.DialSentinel(
"mymaster", // sentinel 网络名
sentinelAddrs, // sentinel 地址列表
"", "", // username/password
)
return
},
}
defer pool.Close()
```
```bash
# Sentinel CLI 查看当前 master 信息
127.0.0.1:26379> SENTINEL get-master-addr-by-name mymaster
1) "192.168.1.10"
2) "6379" # 如果返回空数组,说明正在故障转移中
```
## 实践场景
1. **Sentinel 部署最佳实践**:至少 3 个(奇数),跨机房部署避免单机房断电导致全部失联。quorum 设为 n/2 + 1。
2. **客户端感知的 Sentinel 连接**:Java 的 Jedis / Lettuce 和 Go 的 redigo 都内置了 Sentinel 发现逻辑,配置好 master name 即可自动路由。不要把 master IP 写死在配置里。
3. **Cluster 扩缩容**:新增节点时先 `CLUSTER MEET` 加入集群,再通过 `CLUSTER ADDSLOT` 分配槽位,最后用 `redis-cli --cluster rebalance` 自动均衡。整个过程业务零停机。
4. **脑裂风险**:当网络和物理隔离导致两个"主"共存时,会产生数据分裂。可通过 `min-replicas-to-write` 和 `min-replicas-max-lag` 降低风险。
## 关联笔记
- [[03.Redis/core/Redis 五大核心数据结构]]
- [[03.Redis/core/RDB 与 AOF 持久化]]
- [[03.Redis/strategies/多级缓存架构设计]]