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