# 高可用方案

> Redis 高可用是生产环境的必备能力，主要通过主从复制、哨兵和集群三种方案实现。

## 三种方案对比 ⭐⭐⭐⭐⭐

| 方案       | 高可用 | 自动故障转移 | 水平扩展 | 适用场景      |
| -------- | --- | ------ | ---- | --------- |
| **主从复制** | ❌   | ❌      | ❌    | 读写分离      |
| **哨兵模式** | ✅   | ✅      | ❌    | 中小规模，自动容灾 |
| **集群模式** | ✅   | ✅      | ✅    | 大规模，海量数据  |

***

## 主从复制 ⭐⭐⭐⭐⭐

### 核心原理

主从复制实现**读写分离**和**数据备份**。

```
┌─────────┐  写请求
│  Master │ ◄────────  Client
└────┬────┘
     │ 数据同步
     ├──────────┐
     ▼          ▼
┌─────────┐  ┌─────────┐  读请求
│ Slave 1 │  │ Slave 2 │ ◄────────  Client
└─────────┘  └─────────┘
```

**配置从节点**：

```bash
# 从节点执行
REPLICAOF master_ip master_port
# 或配置文件
replicaof 192.168.1.100 6379
```

### 复制流程 ⭐⭐⭐⭐⭐

**全量同步**（首次连接或重连）：

```
1. Slave 发送 PSYNC 命令
2. Master 执行 BGSAVE 生成 RDB
3. Master 发送 RDB 文件给 Slave
4. Slave 清空旧数据，加载 RDB
5. Master 发送 RDB 生成期间的增量命令
```

**增量同步**（正常运行）：

```
1. Master 执行写命令
2. 命令写入 replication buffer
3. 异步发送给所有 Slave
4. Slave 执行命令，保持数据一致
```

### 复制偏移量与积压缓冲区

**replication backlog**（复制积压缓冲区）：

* 固定大小的环形队列，默认 1MB
* 记录最近的写命令
* 用于**断线重连后的部分重同步**

**复制偏移量**：

* Master 和 Slave 各自维护偏移量
* Slave 断线重连时，通过偏移量判断是否需要全量同步

```bash
# 配置积压缓冲区大小
repl-backlog-size 1mb
```

### 主从复制的问题

1. **不支持自动故障转移**：Master 宕机需手动切换
2. **写能力受限**：只有一个 Master 可写
3. **存储容量受限**：单机内存限制

***

## 哨兵模式 ⭐⭐⭐⭐⭐

### 架构

哨兵（Sentinel）是独立进程，监控 Redis 实例并**自动故障转移**。

```
┌──────────┐  ┌──────────┐  ┌──────────┐
│Sentinel 1│  │Sentinel 2│  │Sentinel 3│  ← 哨兵集群（奇数个）
└────┬─────┘  └────┬─────┘  └────┬─────┘
     └──────────┬──────────────┘
                ▼ 监控
         ┌─────────┐
         │  Master │
         └────┬────┘
              │ 主从复制
         ┌────┴────┐
         ▼         ▼
    ┌────────┐ ┌────────┐
    │ Slave 1│ │ Slave 2│
    └────────┘ └────────┘
```

### 核心功能

1. **监控**：检查 Master 和 Slave 是否正常
2. **通知**：通过 API 通知管理员故障信息
3. **故障转移**：自动将 Slave 提升为 Master
4. **配置中心**：客户端通过哨兵获取 Master 地址

### 故障转移流程 ⭐⭐⭐⭐⭐

```
1. 主观下线（SDOWN）
   - 某个哨兵认为 Master 下线（超时未响应 PING）

2. 客观下线（ODOWN）
   - 达到法定数量（quorum）的哨兵都认为 Master 下线

3. 选举 Leader 哨兵
   - 哨兵之间通过 Raft 算法选举 Leader
   - Leader 负责执行故障转移

4. 选择新 Master
   - 优先级最高（replica-priority）
   - 复制偏移量最大（数据最新）
   - run_id 最小

5. 执行故障转移
   - 向选中的 Slave 发送 SLAVEOF NO ONE（提升为 Master）
   - 其他 Slave 执行 REPLICAOF 新 Master
   - 通知客户端新 Master 地址
```

### 关键配置

```bash
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2  # 2 = quorum
sentinel down-after-milliseconds mymaster 5000  # 5秒判定下线
sentinel failover-timeout mymaster 60000        # 故障转移超时
```

### 哨兵模式的局限

1. **写能力和存储容量无法扩展**：仍是单 Master
2. **配置复杂**：需要部署多个哨兵进程
3. **不支持数据分片**：所有数据在一个 Master

***

## 集群模式 ⭐⭐⭐⭐⭐

### 架构

Redis Cluster 通过**分片**实现水平扩展和高可用。

```
数据分片：16384 个槽（slot）分配到多个 Master

┌─────────────────────────────────────────────────────┐
│          Master 1        Master 2        Master 3   │
│          (0-5460)       (5461-10922)  (10923-16383) │
│             │               │               │       │
│          Slave 1         Slave 2        Slave 3     │
└─────────────────────────────────────────────────────┘

槽分配算法：CRC16(key) % 16384 = slot
```

### 槽分配 ⭐⭐⭐⭐⭐

**哈希槽**：Redis Cluster 将数据分为 16384 个槽（slot）。

**计算公式**：

```
slot = CRC16(key) % 16384
```

**Hash Tag**（强制相同槽）：

```bash
# {user}123 和 {user}456 会被分配到同一个槽
SET {user}123 "data1"
SET {user}456 "data2"
```

### 请求路由 ⭐⭐⭐⭐

**MOVED 重定向**：

```
1. 客户端向节点 A 发送请求
2. 节点 A 发现槽不在自己这里
3. 返回 MOVED 错误，告知正确节点
4. 客户端缓存槽-节点映射，下次直接请求正确节点
```

**ASK 重定向**（槽迁移中）：

```
槽正在从节点 A 迁移到节点 B：
- 如果 key 已迁移 → 返回 ASK，客户端临时请求节点 B
- 如果 key 未迁移 → 节点 A 处理
```

### 扩容与缩容

**添加节点**：

```bash
# 1. 启动新节点
redis-server --cluster-enabled yes

# 2. 加入集群
redis-cli --cluster add-node 新节点IP:端口 已有节点IP:端口

# 3. 重新分配槽
redis-cli --cluster reshard 集群任意节点IP:端口
```

**槽迁移流程**：

```
1. 向目标节点发送 CLUSTER SETSLOT <slot> IMPORTING
2. 向源节点发送 CLUSTER SETSLOT <slot> MIGRATING
3. 逐个迁移槽中的 key（MIGRATE 命令）
4. 通知所有节点槽的新归属
```

### 故障转移

**自动故障转移**（无需哨兵）：

```
1. 集群中超过半数 Master 认为某个 Master 下线
2. 该 Master 的 Slave 发起选举
3. 获得多数投票的 Slave 晋升为 Master
4. 新 Master 接管槽，继续提供服务
```

### 集群模式的限制

1. **不支持多数据库**：只能使用 db0
2. **批量操作受限**：MGET/MSET 的 key 必须在同一槽
3. **事务受限**：事务中的 key 必须在同一槽
4. **复制结构固定**：只支持一层主从，不支持级联

***

## 方案选型 ⭐⭐⭐⭐⭐

### 场景推荐

| 场景                | 推荐方案    | 理由           |
| ----------------- | ------- | ------------ |
| **小规模，QPS < 10W** | 主从 + 哨兵 | 配置简单，满足需求    |
| **中等规模，需自动容灾**    | 哨兵模式    | 自动故障转移，运维成本低 |
| **大规模，数据量大**      | 集群模式    | 水平扩展，突破单机限制  |
| **纯缓存，可丢失数据**     | 主从复制    | 成本最低         |

### 对比总结

```
主从复制：最简单，但无容灾能力
哨兵模式：主从 + 自动故障转移
集群模式：哨兵 + 分片 + 水平扩展

进化路线：主从 → 哨兵 → 集群
```

***

## 面试要点 ⭐⭐⭐⭐⭐

**Q1: 主从复制的全量同步和增量同步如何区分？**

* 全量同步：首次连接或 replication backlog 无法覆盖时，传输整个 RDB
* 增量同步：正常运行时，Master 异步发送写命令给 Slave

**Q2: 哨兵如何判断 Master 下线？**

* 主观下线（SDOWN）：单个哨兵超时未收到 PING 响应
* 客观下线（ODOWN）：达到 quorum 数量的哨兵都认为下线

**Q3: 哨兵如何选择新 Master？**

1. 优先级最高（replica-priority）
2. 复制偏移量最大（数据最新）
3. run\_id 最小（启动时间最早）

**Q4: Redis Cluster 为什么是 16384 个槽？**

* 足够大，支持 1000+ 节点
* 槽信息通过心跳传输，16384 个槽的位图只需 2KB
* 太大会增加心跳包大小

**Q5: 集群模式下如何执行 MGET？**

* 方案 1：客户端拆分为多个单 key 请求
* 方案 2：使用 Hash Tag 强制 key 在同一槽
* 方案 3：使用支持集群的客户端（如 Jedis Cluster）

**Q6: 哨兵和集群的故障转移有什么区别？**

* 哨兵：依赖独立的哨兵进程，通过 Raft 选举 Leader
* 集群：Master 节点内置故障检测，Slave 自主发起选举

**Q7: 为什么不推荐使用主从复制而不用哨兵？**

* 主从复制无自动故障转移，Master 宕机需手动切换，可用性差

***

## 参考资料

1. **官方文档**：[Redis Replication](https://redis.io/docs/management/replication/)、[Redis Sentinel](https://redis.io/docs/management/sentinel/)、[Redis Cluster](https://redis.io/docs/management/scaling/)
2. **书籍推荐**：《Redis 设计与实现》、《Redis 开发与运维》
