Files

208 lines
7.8 KiB
Markdown
Raw Permalink 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: [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/多级缓存架构设计]]