Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

中间件 / Middleware

1. Web 中间件全景 / The Web Middleware Landscape

2. 中间件——流量洪峰下的系统保护 / Middleware for Protecting Systems Under Traffic Spikes

cache layer

1. 缓存架构全景 / Cache Architecture Overview

2. Redis 深入——为什么你的缓存总是出问题 / Redis Internals and Cache Failure Modes

3. Redis 数据结构场景应用——什么时候用什么 / Choosing Redis Data Structures for Real Applications

4. 缓存策略——库存变了,缓存怎么处理 / Cache Strategies and Inventory Consistency

5. Redis 缓存三剑客——穿透/击穿/雪崩 + 一致性策略 / Redis Cache Penetration, Breakdown, Avalanche, and Consistency

6. Redis 分布式锁——从 SETNX 到 Redisson / Redis Distributed Locks from SETNX to Redisson

7. Redis Cluster 与 Sentinel 高可用架构 / Redis Cluster and Sentinel High-Availability Architecture

8. Redis 高级特性——Stream / PubSub / Module / LLM 应用

9. Redis 架构深度分析——为什么 Redis 能这么快 / Redis Architecture and the Sources of Its Performance

10. Redis 全景——为什么你的系统需要一个缓存层 / The Redis Landscape and Why Systems Need a Cache Layer

message queue

1. 消息队列全景——为什么你的系统需要一个中间人 / Message Queue Overview and Why Your System Needs a Middleman

2. Kafka 核心——为什么你的消息总是"丢"了 / Kafka Fundamentals and Message Delivery Semantics

3. 消息队列高级——死信队列、延迟消息、消息积压 / Advanced Messaging with Dead Letters, Delays, and Backlogs

4. Kafka Streams 与 Connect —— 让数据自己流动起来 / Kafka Streams and Connect for Streaming Data Pipelines

5. RabbitMQ 深度解析 —— 灵活路由与消息可靠性 / RabbitMQ Deep Dive into Flexible Routing and Reliability

6. 消息队列对比——为什么最终选了 Kafka / Comparing Message Queues and Choosing Kafka

search engine

1. 搜索引擎知识体系 / Search Engine Knowledge System

2. Elasticsearch——为什么 Like 查询总是那么慢 / Elasticsearch for Full-Text Search at Scale

3. Elasticsearch 查询 DSL 深入——为什么你的搜索总是不准 / Elasticsearch Query DSL Deep Dive

4. Elasticsearch 集群规划与运维——为什么你的集群总是"黄" / Elasticsearch Cluster Planning and Operations

5. Meilisearch 与轻量搜索替代方案——当 ES 太重时 / Meilisearch and Lightweight Search Alternatives

infrastructure

1. 基础设施组件全景 / Infrastructure Components Landscape

2. Nginx 与反向代理 / Nginx and Reverse Proxy

3. 服务发现 / Service Discovery

4. 配置中心 / Configuration Center

本页目录

Redis Cluster 与 Sentinel 高可用架构 / Redis Cluster and Sentinel High-Availability Architecture ​

📅 创建时间:2026-07-28 🏷️ 标签:#Redis #Sentinel #Cluster #高可用 #数据分片 #HashSlots #Gossip 📚 前置知识:[[/03-web/07-middleware/01-cache-layer/00-overview]]


📋 本章目标 ​

  • 理解为什么单机 Redis 不够用:内存上限、单点故障、写压力三个瓶颈
  • 掌握 Redis Sentinel 的完整架构:监控、通知、自动故障转移、配置提供者
  • 理解主观下线(SDOWN)与客观下线(ODOWN)的区别,以及 Quorum 投票机制
  • 掌握 Redis Cluster 的数据分片原理:16384 个 hash slots、key 到 slot 的映射
  • 理解节点间 Gossip 协议如何传播集群元数据
  • 能解释 MOVED 重定向与 ASK 重定向的触发条件和客户端行为差异
  • 能够用 ioredis Cluster 模式或 Jedis Cluster 写出正确的客户端集成代码
  • 掌握集群运维的核心操作:扩容、缩容、槽迁移、节点替换
  • 能够在 Sentinel 和 Cluster 之间做出正确的选型决策

第1部分:为什么需要集群 ​

1.1 单机 Redis 的三个天花板 ​

┌─────────────────────────────────────────────────────────────┐
│              单机 Redis 的三大瓶颈                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  瓶颈1:内存上限                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  一台物理机/虚拟机的内存是有限的(如 64GB)             │   │
│  │  业务数据量超过 64GB → 单机放不下                      │   │
│  │                                                      │   │
│  │  压力:数据量大 ≠ 请求量大                            │   │
│  │  解法:把数据切分到多台机器(分片/Sharding)           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  瓶颈2:单点故障(SPOF)                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  单台 Redis 宕机 → 所有缓存不可用                     │   │
│  │  → 请求全部穿透到数据库 → 数据库被打垮                │   │
│  │                                                      │   │
│  │  压力:可用性风险                                     │   │
│  │  解法:主从复制 + 自动故障转移(Sentinel)             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  瓶颈3:写压力                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Redis 单线程处理命令(主线程)                        │   │
│  │  单机写 QPS 上限 ≈ 10万(视操作复杂度)               │   │
│  │  业务增长超过这个上限 → 单机写不动                    │   │
│  │                                                      │   │
│  │  压力:写入吞吐量不足                                 │   │
│  │  解法:多个主节点分担写入(Cluster 多 Master)         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  三个瓶颈的对应解法:                                       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  瓶颈           │ 解法              │ 对应架构        │   │
│  │  ──────────────┼──────────────────┼───────────────── │   │
│  │  内存不够       │ 数据分片          │ Redis Cluster   │   │
│  │  单点故障       │ 主从+故障转移     │ Sentinel/Cluster│   │
│  │  写不动         │ 多主分担写入      │ Redis Cluster   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

1.2 从单机到集群的演进路径 ​

┌─────────────────────────────────────────────────────────────┐
│              Redis 架构演进路径                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  阶段1:单机                                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  [Redis]                                            │   │
│  │  一台机器,无备份                                    │   │
│  │  挂了就全挂                                          │   │
│  └─────────────────────────────────────────────────────┘   │
│     │                                                       │
│     ↓ 问题:单点故障                                        │
│                                                             │
│  阶段2:主从复制(Master-Slave)                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  [Master] ←──写                                      │   │
│  │     │ 异步复制                                        │   │
│  │     ├──→ [Slave1] ←──读                              │   │
│  │     └──→ [Slave2] ←──读                              │   │
│  │                                                      │   │
│  │  读写分离:主写从读                                   │   │
│  │  问题:Master 挂了需要手动切换,有数据丢失窗口        │   │
│  └─────────────────────────────────────────────────────┘   │
│     │                                                       │
│     ↓ 问题:手动故障切换太慢                                │
│                                                             │
│  阶段3:Sentinel(高可用)                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  [Sentinel1] [Sentinel2] [Sentinel3]  ← 哨兵集群     │   │
│  │       │          │          │                        │   │
│  │       └──────────┴──────────┘                        │   │
│  │                  │                                    │   │
│  │      监控 + 自动故障转移                              │   │
│  │                  │                                    │   │
│  │  [Master] ←→ [Slave1] [Slave2]                      │   │
│  │                                                      │   │
│  │  解决了:自动故障转移,但所有数据还是在一台 Master上  │   │
│  └─────────────────────────────────────────────────────┘   │
│     │                                                       │
│     ↓ 问题:单机内存有限、单主写不动                        │
│                                                             │
│  阶段4:Cluster(分片 + 高可用)                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  [Master1]    [Master2]    [Master3]                │   │
│  │   Slots 0-5k   Slots 5k-10k Slots 10k-16k          │   │
│  │     │             │             │                     │   │
│  │  [Slave1]     [Slave2]     [Slave3]                 │   │
│  │                                                      │   │
│  │  每个 Master 负责一部分数据,内置故障转移             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

第2部分:Redis Sentinel 架构 ​

2.1 Sentinel 是什么 ​

Sentinel 是一个独立的进程,它的唯一使命是:替你盯着 Redis 实例,Master 挂了自动把 Slave 提上来。

Sentinel 本身也要部署多个(至少 3 个),组成哨兵集群,因为单个 Sentinel 自己也会挂。

┌─────────────────────────────────────────────────────────────┐
│              Redis Sentinel 完整拓扑                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                   Sentinel 集群                        │  │
│  │                                                       │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │  │
│  │  │Sentinel1 │  │Sentinel2 │  │Sentinel3 │            │  │
│  │  │ :26379   │  │ :26380   │  │ :26381   │            │  │
│  │  └─────┬────┘  └─────┬────┘  └─────┬────┘            │  │
│  │        └──────────────┼─────────────┘                 │  │
│  │                       │ 互相通信 (Pub/Sub)             │  │
│  └───────────────────────┼──────────────────────────────┘  │
│                          │                                  │
│               ┌──────────┴──────────┐                       │
│               ↓ 监控                 ↓ 监控                  │
│  ┌──────────────────────┐  ┌──────────────────────┐        │
│  │  Redis Master        │  │  Redis Slave         │        │
│  │  :6379               │  │  :6380               │        │
│  │  role: master        │  │  role: slave         │        │
│  │  slaveof: -          │  │  master_host: :6379  │        │
│  └──────────────────────┘  └──────────────────────┘        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

2.2 Sentinel 的四个职责 ​

┌─────────────────────────────────────────────────────────────┐
│              Sentinel 的四大职责                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 监控(Monitoring)                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Sentinel 定期 PING Redis 实例(Master + Slave)     │   │
│  │  如果 Master 在指定时间内没有响应 → 标记为主观下线  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  2. 通知(Notification)                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  故障转移完成后,Sentinel 通过 Pub/Sub 通知客户端     │   │
│  │  客户端收到通知后更新连接目标为新的 Master            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  3. 自动故障转移(Automatic Failover)                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  确认 Master 客观下线后:                            │   │
│  │    a. Sentinel 之间选举一个 Leader                   │   │
│  │    b. Leader 从 Slave 中选一个提升为新 Master        │   │
│  │    c. 让其他 Slave 改为复制新 Master                 │   │
│  │    d. 旧 Master 恢复后自动降为 Slave                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  4. 配置提供者(Configuration Provider)                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  客户端连接 Sentinel,询问"当前 Master 是谁?"        │   │
│  │  Sentinel 返回 Master 地址                           │   │
│  │  这样客户端不需要硬编码 Master 地址                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

2.3 主观下线(SDOWN)vs 客观下线(ODOWN) ​

这是 Sentinel 最核心的判断机制:

┌─────────────────────────────────────────────────────────────┐
│            SDOWN vs ODOWN 判断流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  SDOWN(Subjectively Down,主观下线)                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Sentinel1 PING Master                              │   │
│  │  超时(down-after-milliseconds,默认30s)            │   │
│  │  → Sentinel1 认为 Master 挂了                        │   │
│  │  → 但这只是 Sentinel1 自己的判断,可能是网络抖动!   │   │
│  │  → 标记: SDOWN                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ODOWN(Objectively Down,客观下线)                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Sentinel1 发现 Master SDOWN 后:                    │   │
│  │   1. 向其他 Sentinel 询问:"你们觉得 Master 挂了没?" │   │
│  │   2. 收集投票结果                                     │   │
│  │   3. 如果 ≥ quorum 个 Sentinel 都认为 Master SDOWN   │   │
│  │   4. → 标记: ODOWN                                  │   │
│  │   5. → 触发故障转移                                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  关键参数:                                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  down-after-milliseconds: 认为无响应的超时时间       │   │
│  │  quorum: 需要多少个 Sentinel 同意才能标记 ODOWN       │   │
│  │                                                      │   │
│  │  示例配置(3 个 Sentinel):                          │   │
│  │  quorum = 2                                          │   │
│  │  含义:至少 2 个 Sentinel 认为 Master 挂了,          │   │
│  │        才算真正挂了,才触发故障转移                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ODOWN 的时序图:                                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  S1: PING Master ──超时──→ SDOWN                     │   │
│  │   │                                                  │   │
│  │   ├──→ S2: "is-master-down?" ──→ 回复: yes (SDOWN)   │   │
│  │   └──→ S3: "is-master-down?" ──→ 回复: yes (SDOWN)   │   │
│  │                                                      │   │
│  │  投票结果: 2/2 >= quorum(2)                          │   │
│  │  → ODOWN! 开始故障转移                               │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

2.4 Leader 选举与故障转移流程 ​

标记 ODOWN 后,Sentinel 之间要选举出一个 Leader 来执行故障转移:

┌─────────────────────────────────────────────────────────────┐
│              Sentinel Leader 选举与 Failover 流程             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  选举流程(类 Raft 协议):                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. 发现 ODOWN 的 Sentinel 发起选举                  │   │
│  │     → 给自己投一票                                   │   │
│  │     → 向其他 Sentinel 发送投票请求                   │   │
│  │                                                      │   │
│  │  2. 其他 Sentinel 响应规则(先到先得)                │   │
│  │     → 如果还没投过票,投给请求者                     │   │
│  │     → 如果已经投过了,拒绝                           │   │
│  │                                                      │   │
│  │  3. 得票超过 max(quorum, N/2+1) → 成为 Leader        │   │
│  │     N=Sentinel实例数                                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Failover 流程(Leader 执行):                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  Step 1: 选新 Master                                 │   │
│  │  ┌──────────────────────────────────────────────┐    │   │
│  │  │  从所有 Slave 中选最优的:                     │    │   │
│  │  │  优先级1: slave-priority(数字越小越优先)      │    │   │
│  │  │  优先级2: 复制偏移量(谁复制的最新)            │   │   │
│  │  │  优先级3: run_id(字典序,兜底)                │   │   │
│  │  └──────────────────────────────────────────────┘    │   │
│  │                                                      │   │
│  │  Step 2: SLAVEOF NO ONE                              │   │
│  │  ┌──────────────────────────────────────────────┐    │   │
│  │  │  向选中的 Slave 发送命令,让它脱离主从关系     │    │   │
│  │  │  它现在是新的 Master                          │    │   │
│  │  └──────────────────────────────────────────────┘    │   │
│  │                                                      │   │
│  │  Step 3: 其他 Slave 重新指向                         │   │
│  │  ┌──────────────────────────────────────────────┐    │   │
│  │  │  向其余 Slave 发送 SLAVEOF new-master          │    │   │
│  │  │  让它们开始复制新 Master                        │    │   │
│  │  └──────────────────────────────────────────────┘    │   │
│  │                                                      │   │
│  │  Step 4: 旧 Master 恢复后降级                        │   │
│  │  ┌──────────────────────────────────────────────┐    │   │
│  │  │  旧 Master 重连后,Sentinel 发 SLAVEOF          │    │   │
│  │  │  让它变成新 Master 的 Slave                     │    │   │
│  │  └──────────────────────────────────────────────┘    │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

2.5 Sentinel 配置实战 ​

bash
# sentinel.conf — 每个 Sentinel 实例一个配置文件

# 监控 Master: sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# Master 无响应超过 30 秒则标记 SDOWN
sentinel down-after-milliseconds mymaster 30000

# Failover 超时时间(超过则认为 failover 失败)
sentinel failover-timeout mymaster 180000

# 同时有多少个 Slave 可以和新 Master 同步
sentinel parallel-syncs mymaster 1
1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│              Sentinel 关键参数说明                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  down-after-milliseconds                                    │
│  ├── SDOWN 的超时判断依据                                   │
│  ├── 设太小:网络抖动就触发 SDOWN,产生误判                 │
│  ├── 设太大:Master 真挂了很久才发现                         │
│  └── 推荐:在云环境可设 30s;物理机房可设 10s               │
│                                                             │
│  parallel-syncs                                             │
│  ├── 故障转移后,多少 Slave 同时从新 Master 全量同步        │
│  ├── 设太大:新 Master 的带宽/CPU 被同步占满                 │
│  ├── 设太小:Slave 逐个同步,最后一个等很久                  │
│  └── 推荐:1(逐个同步,避免 Master 过载)                  │
│                                                             │
│  failover-timeout                                           │
│  ├── 故障转移各阶段的超时时间                                │
│  ├── 如果超时,这次 failover 失败,下次重新选举 Leader      │
│  └── 推荐:180000(3分钟)                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

第3部分:Redis Cluster 架构 ​

3.1 Cluster 的核心思想:Hash Slot ​

┌─────────────────────────────────────────────────────────────┐
│              16384 个 Hash Slots 的分片模型                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  核心规则:                                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. Redis Cluster 共有 16384 个 slot(哈希槽)       │   │
│  │  2. 每个 key 通过 CRC16(key) % 16384 归属到一个 slot │   │
│  │  3. 每个 Master 节点负责一部分 slot                   │   │
│  │  4. 数据在哪个节点 = slot 在哪个节点                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  为什么是 16384?                                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. 心跳包大小:节点间 Gossip 消息携带 slot 位图     │   │
│  │     16384 bits = 2KB,刚好塞进一个 TCP 包            │   │
│  │  2. 节点数上限:Redis 建议集群不超过 1000 个节点     │   │
│  │     16384 slots / 1000 nodes = 每个节点 ~16 slots    │   │
│  │     粒度足够细,迁移时可以逐个 slot 搬                │   │
│  │  3. CRC16 输出 16 位,16384 = 2^14,不多不少        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  3 Master 的 Slot 分布示例:                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  Master1: Slots 0    ─── 5460    (5461 个 slot)     │   │
│  │  Master2: Slots 5461 ─── 10922   (5462 个 slot)     │   │
│  │  Master3: Slots 10923─── 16383   (5461 个 slot)     │   │
│  │                                                      │   │
│  │  key "user:1001"                                     │   │
│  │  → CRC16("user:1001") = 12345                       │   │
│  │  → 12345 % 16384 = 12345                            │   │
│  │  → slot 12345 在 Master3 上                          │   │
│  │  → 请求发到 Master3                                  │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

3.2 Hash Tag:让多个 Key 落在同一个 Slot ​

┌─────────────────────────────────────────────────────────────┐
│              Hash Tag 机制                                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  问题:                                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  事务/批处理/ Lua 脚本需要多个 key 在同一节点         │   │
│  │  但 hash slot 计算会把它们分散到不同节点              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  解决:Hash Tag                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  规则:CRC16 只计算 { 和 } 之间的内容                 │   │
│  │                                                      │   │
│  │  user:{1001}:name    ─┐                              │   │
│  │  user:{1001}:email    ├── 都算 CRC16("1001")         │   │
│  │  user:{1001}:orders  ─┘   → 保证在同一 slot          │   │
│  │                                                      │   │
│  │  示例:                                              │   │
│  │  MSET user:{1001}:name "Tom" user:{1001}:age 25     │   │
│  │  → 两个 key 同 slot → 同节点 → 原子执行              │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  注意:                                                     │
│  ├── 不要滥用 hash tag 导致数据倾斜                        │
│  ├── 如果 {1001} 的数据量巨大,该 slot 成为热点            │
│  └── 正常情况下让 key 自然分散到各节点                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

3.3 Gossip 协议:节点间如何通信 ​

Redis Cluster 的节点之间不通过中心化组件通信,而是通过 Gossip(流言)协议互相传递集群元数据:

┌─────────────────────────────────────────────────────────────┐
│              Gossip 协议通信机制                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  原理:每个节点定期随机选取几个其他节点,交换自己知道的元数据 │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │   Node1  ──── Gossip ────→  Node2                   │   │
│  │   "我知道: Node3 的 slots 是 0-5000"                 │   │
│  │   "我知道: Node4 还活着"                             │   │
│  │                                                      │   │
│  │   Node2 收到后更新自己的路由表,                      │   │
│  │   下次 Gossip 时再传给 Node3 和 Node4               │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Gossip 消息类型:                                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  PING ──→ 发送心跳,附带自己知道的节点信息            │   │
│  │  PONG ←── 回复心跳,附带发送者知道的节点信息          │   │
│  │  MEET ──→ 邀请新节点加入集群                          │   │
│  │  FAIL ──→ 广播某个节点已确认挂掉                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  节点故障判断(Cluster 内置,不依赖 Sentinel):            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. Node1 PING Node2 → 超时                         │   │
│  │     → Node1 标记 Node2 为 PFAIL(疑似下线)          │   │
│  │                                                      │   │
│  │  2. Node1 通过 Gossip 传播 "Node2 可能是 PFAIL"      │   │
│  │                                                      │   │
│  │  3. 当 ≥ N/2+1 个 Master 都认为 Node2 是 PFAIL      │   │
│  │     → 所有节点标记 Node2 为 FAIL(确认下线)          │   │
│  │     → 触发故障转移                                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Gossip 的消息传播速度:                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  假设 100 个节点,每个节点每秒 ping 1 个随机节点     │   │
│  │  一条新消息在 ~log(100) ≈ 5 秒内传遍整个集群         │   │
│  │                                                     │   │
│  │  代价:每个节点每秒发 1 条 Gossip 消息               │   │
│  │  100 节点全部 Gossip ≈ 100 条/秒                     │   │
│  │  足够低,不占多少带宽                                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

3.4 Cluster 的故障转移 ​

Redis Cluster 内置故障转移,不需要额外部署 Sentinel:

┌─────────────────────────────────────────────────────────────┐
│              Cluster 故障转移流程                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  拓扑:                                               │   │
│  │  Master1 (slots 0-5000)  ←→  Slave1                 │   │
│  │  Master2 (slots 5001-10000) ←→ Slave2               │   │
│  │  Master3 (slots 10001-16383) ←→ Slave3              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Master1 宕机后:                                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. Master2 / Master3 通过 Gossip 发现 Master1 PFAIL │   │
│  │  2. 足够多的 Master 确认 → Master1 标记 FAIL         │   │
│  │  3. Slave1 发现自己的 Master FAIL                     │   │
│  │  4. Slave1 发起选举(向其他 Master 请求投票)         │   │
│  │  5. Slave1 获得 N/2+1 票 → 提升为新 Master           │   │
│  │  6. Slave1 接管 slots 0-5000                          │   │
│  │  7. 新 Master1 通过 Gossip 广播自己的新身份           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Cluster 选举的条件:                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Slave 要参与竞选,需要满足:                         │   │
│  │  1. 它的 Master 处于 FAIL 状态                        │   │
│  │  2. 它与 Master 的复制偏移量足够新                    │   │
│  │     (epoch 时间内落后不超过一定量)                  │   │
│  │  3. 集群半数以上 Master 给它投票                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

第4部分:MOVED 与 ASK 重定向 ​

4.1 问题的由来 ​

┌─────────────────────────────────────────────────────────────┐
│              客户端访问 Cluster 的问题                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  客户端 ──→ GET user:1001 ──→ Node1                 │   │
│  │                                                      │   │
│  │  但 key "user:1001" 的 hash slot 在 Node2!          │   │
│  │                                                      │   │
│  │  Node1 怎么回复?                                    │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

4.2 MOVED:永久重定向 ​

┌─────────────────────────────────────────────────────────────┐
│              MOVED 重定向(永久)                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  触发条件:key 所在的 slot 在另一个节点上,且没有迁移       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  客户端 ──→ GET user:1001 ──→ Node1                 │   │
│  │                                      │               │   │
│  │         ←── MOVED 12345 192.168.1.2:6379 ──┘        │   │
│  │              │         │                             │   │
│  │              │         └── Node2 的地址              │   │
│  │              └── slot 编号                           │   │
│  │                                                      │   │
│  │  客户端收到 MOVED 后:                               │   │
│  │  1. 更新本地 slot → 节点映射表                        │   │
│  │  2. 重新向 Node2 发送 GET user:1001                  │   │
│  │  3. 以后对 slot 12345 的请求直接发 Node2             │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  特点:                                                     │
│  ├── 永久重定向:客户端应更新路由表                         │
│  ├── 说明本次请求发错了节点                                 │
│  └── 下次同一 slot 的请求不会再 MOVED                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

4.3 ASK:临时重定向 ​

┌─────────────────────────────────────────────────────────────┐
│              ASK 重定向(临时,槽迁移中)                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  触发条件:key 所在的 slot 正在从 Node1 迁移到 Node2       │
│                                                             │
│  场景:Slot 12345 正在从 Node1 迁移到 Node2                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  迁移中的 slot 状态:                                │   │
│  │  ┌──────────────────────────────────────────────┐   │   │
│  │  │  Node1 (源): slot 12345 → MIGRATING           │   │   │
│  │  │  Node2 (目标): slot 12345 → IMPORTING         │   │   │
│  │  └──────────────────────────────────────────────┘   │   │
│  │                                                      │   │
│  │  客户端 ──→ GET user:1001 ──→ Node1                 │   │
│  │                                                      │   │
│  │  Node1 查找 user:1001:                              │   │
│  │  ├── 找到了 → 直接返回(迁移中但数据还在就返回)     │   │
│  │  └── 没找到(已迁移到 Node2)→ 返回:                │   │
│  │      ASK 12345 192.168.1.2:6379                     │   │
│  │                                                      │   │
│  │  客户端收到 ASK 后:                                 │   │
│  │  1. 向 Node2 发送 ASKING 命令                        │   │
│  │     (告诉 Node2:虽然 slot 状态是 IMPORTING,       │   │
│  │      但我确认要操作你这个 slot)                     │   │
│  │  2. 然后发送 GET user:1001                           │   │
│  │  3. 不更新本地路由表!(这是临时状态)               │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ASK vs MOVED 对比:                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              │ MOVED        │ ASK                    │   │
│  │  ────────────┼──────────────┼──────────────────────  │   │
│  │  场景        │ 正常路由      │ 槽迁移中               │   │
│  │  slot 状态   │ 稳定归属      │ MIGRATING/IMPORTING   │   │
│  │  重定向类型  │ 永久          │ 临时(仅本次请求)     │   │
│  │  是否更新路由│ 是            │ 否                     │   │
│  │  前置命令    │ 无            │ ASKING                 │   │
│  │  效率        │ 高(更新后直连│ 低(每次可能都 ASK)    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

4.4 智能客户端如何高效路由 ​

┌─────────────────────────────────────────────────────────────┐
│              智能客户端路由流程                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  客户端启动:                                         │   │
│  │  1. 连接种子节点 (seed nodes),发送 CLUSTER SLOTS    │   │
│  │  2. 获取完整的 slot → node 映射表                    │   │
│  │  3. 在本地缓存这份映射表                              │   │
│  │                                                      │   │
│  │  每次请求:                                           │   │
│  │  1. CRC16(key) % 16384 → slot                       │   │
│  │  2. 从本地映射表查 slot → node                      │   │
│  │  3. 直接向目标 node 发送命令                          │   │
│  │                                                      │   │
│  │  收到 MOVED:                                         │   │
│  │  1. 更新本地映射表                                   │   │
│  │  2. 重试请求                                         │   │
│  │                                                      │   │
│  │  收到 ASK:                                           │   │
│  │  1. 向目标节点 ASKING + 原命令                        │   │
│  │  2. 不更新本地映射表                                  │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  为什么不在服务端做路由转发?                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Redis Cluster 没有 Proxy 层                         │   │
│  │  每个节点都知道完整路由表,但不做转发                  │   │
│  │  转发会成为瓶颈、增加延迟、破坏无中心架构             │   │
│  │  所以把路由责任交给客户端                             │   │
│  │                                                     │   │
│  │  如果你想要 Proxy 层 → 用 Redis Cluster Proxy(补充件)│   │
│  │  或 Codis / Twemproxy 等代理方案                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

第5部分:客户端如何集成 ​

5.1 ioredis Cluster 模式(Node.js / TypeScript) ​

typescript
import Redis from 'ioredis';

// 创建 Cluster 客户端
const cluster = new Redis.Cluster([
  { host: '192.168.1.1', port: 6379 },
  { host: '192.168.1.2', port: 6379 },
  { host: '192.168.1.3', port: 6379 },
], {
  // 关键配置
  redisOptions: {
    password: 'your-password',
    connectTimeout: 5000,
  },

  // 集群相关配置
  clusterRetryStrategy: (times) => {
    // 重试策略:指数退避
    return Math.min(times * 100, 3000);
  },

  // 是否在 MOVED 时自动重定向(默认 true)
  enableAutoPipelining: true,

  // 节点宕机后多久从连接池移除
  scaleReads: 'slave', // 'master' | 'slave' | 'all'
});

// 普通操作 —— 客户端自动路由
await cluster.set('user:1001', JSON.stringify({ name: 'Tom' }));
const user = await cluster.get('user:1001');

// Pipeline —— 自动按节点分组
const pipeline = cluster.pipeline();
pipeline.set('key1', 'val1');
pipeline.set('key2', 'val2');
pipeline.get('key1');
const results = await pipeline.exec();
// ioredis 自动把 pipeline 中的命令按 slot 分组,
// 向不同节点发送不同的 pipeline 子集

// 事务(需要 hash tag 保证同一 slot)
await cluster.multi()
  .set('order:{1001}:id', '1001')
  .set('order:{1001}:amount', '99')
  .exec();
// 用 {1001} 确保两个 key 在同一个 slot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

5.2 Jedis Cluster(Java) ​

java
import redis.clients.jedis.*;
import java.util.HashSet;
import java.util.Set;

public class RedisClusterExample {
    public static void main(String[] args) {
        // 配置种子节点
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.1.1", 6379));
        nodes.add(new HostAndPort("192.168.1.2", 6379));
        nodes.add(new HostAndPort("192.168.1.3", 6379));

        JedisCluster cluster = new JedisCluster(
            nodes,
            5000,   // connectionTimeout
            5000,   // soTimeout
            3,      // maxAttempts (MOVED/ASK 重试次数)
            "password",
            new GenericObjectPoolConfig<>()
        );

        // 普通操作 —— 客户端自动路由
        cluster.set("user:1001", "{\"name\":\"Tom\"}");
        String user = cluster.get("user:1001");

        // 注意:JedisCluster 没有 pipeline 支持
        // 需要 pipeline 时考虑使用 Lettuce 客户端

        // 关闭连接
        cluster.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

5.3 Sentinel 客户端集成(ioredis) ​

typescript
import Redis from 'ioredis';

const redis = new Redis({
  sentinels: [
    { host: '192.168.1.1', port: 26379 },
    { host: '192.168.1.2', port: 26379 },
    { host: '192.168.1.3', port: 26379 },
  ],
  name: 'mymaster',       // 对应 sentinel.conf 中的 master-name
  password: 'your-password',
  sentinelPassword: 'sentinel-password',

  // 当 Sentinel 通知 Master 变更时自动切换
  enableAutoPipelining: true,
});

// Sentinel 模式下,客户端:
// 1. 启动时向 Sentinel 询问 Master 地址
// 2. 连接 Master
// 3. 订阅 Sentinel 的 +switch-master 频道
// 4. Master 切换后自动重连新 Master
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

第6部分:集群运维 ​

6.1 扩容:加入新节点 ​

┌─────────────────────────────────────────────────────────────┐
│              Cluster 扩容流程                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  初始状态:3 Masters, 3 Slaves                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  M1 (0-5460)    M2 (5461-10922)   M3 (10923-16383) │   │
│  │  S1             S2                S3                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  目标:加入 M4 + S4,重新平衡 slot                          │
│                                                             │
│  Step 1: 启动新节点                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  redis-server --port 6380 --cluster-enabled yes      │   │
│  │  redis-server --port 6381 --cluster-enabled yes      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Step 2: 加入集群                                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  redis-cli --cluster add-node 192.168.1.4:6380 \     │   │
│  │            192.168.1.1:6379  # 任意已有节点           │   │
│  │                                                      │   │
│  │  redis-cli --cluster add-node 192.168.1.4:6381 \     │   │
│  │            192.168.1.1:6379 \                        │   │
│  │            --cluster-slave \                          │   │
│  │            --cluster-master-id <M4的id>              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Step 3: 重新分配 slot(reshard)                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  redis-cli --cluster reshard 192.168.1.1:6379        │   │
│  │                                                      │   │
│  │  交互式引导:                                        │   │
│  │  How many slots do you want to move? 4096            │   │
│  │  What is the receiving node ID? <M4的id>             │   │
│  │  Source node #1: <M1的id>                            │   │
│  │  Source node #2: <M2的id>                            │   │
│  │  Source node #3: <M3的id>                            │   │
│  │  Source node #4: done                                │   │
│  │                                                      │   │
│  │  从 3 个旧 Master 各搬 ~1365 个 slot 到 M4           │   │
│  │  结果:每个 Master 约 4096 个 slot                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  最终状态:4 Masters, 4 Slaves                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  M1 (0-4095)     M2 (4096-8191)                     │   │
│  │  M3 (8192-12287) M4 (12288-16383)                  │   │
│  │  S1              S2             S3     S4           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

6.2 缩容:移除节点 ​

┌─────────────────────────────────────────────────────────────┐
│              Cluster 缩容流程                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Step 1: 迁移该节点的所有 slot 出去                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  # 将 M4 的 slot 迁移到其他节点                      │   │
│  │  redis-cli --cluster reshard 192.168.1.1:6379        │   │
│  │                                                      │   │
│  │  How many slots do you want to move? 4096            │   │
│  │  What is the receiving node ID? <M1的id>             │   │
│  │  Source node #1: <M4的id>                            │   │
│  │  Source node #2: done                                │   │
│  │                                                      │   │
│  │  也可以平均分配到 M1/M2/M3                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  Step 2: 确认 slot 已清空后再删除节点                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  # 检查 M4 是否还有 slot                              │   │
│  │  redis-cli -p 6380 CLUSTER NODES                     │   │
│  │  # 确保 M4 的 slot 列表为空                           │   │
│  │                                                      │   │
│  │  # 先从集群中移除 Slave                               │   │
│  │  redis-cli --cluster del-node 192.168.1.1:6379 \     │   │
│  │            <S4的id>                                  │   │
│  │                                                      │   │
│  │  # 再移除 Master                                     │   │
│  │  redis-cli --cluster del-node 192.168.1.1:6379 \     │   │
│  │            <M4的id>                                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

6.3 槽迁移的内部流程 ​

┌─────────────────────────────────────────────────────────────┐
│              Slot 迁移的原子操作流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Redis Cluster 对每个 slot 的迁移是按 key 逐个进行的:       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                      │   │
│  │  源节点 (Node1)                   目标节点 (Node2)    │   │
│  │                                                      │   │
│  │  1. Node2: CLUSTER SETSLOT <slot> IMPORTING <node1>  │   │
│  │     → 告诉 Node2: 我要接收 slot 12345 了             │   │
│  │                                                      │   │
│  │  2. Node1: CLUSTER SETSLOT <slot> MIGRATING <node2>  │   │
│  │     → 告诉 Node1: 我要迁移 slot 12345 了             │   │
│  │                                                      │   │
│  │  3. 获取 slot 中的所有 key                            │   │
│  │     CLUSTER GETKEYSINSLOT <slot> <count>             │   │
│  │                                                      │   │
│  │  4. 逐个迁移 key(原子操作)                          │   │
│  │     MIGRATE <target_host> <target_port> <key>        │   │
│  │            <db> <timeout> [COPY] [REPLACE]           │   │
│  │     → 源节点把 key 序列化发给目标                     │   │
│  │     → 目标节点接收并存储                              │   │
│  │     → 源节点删除本地 key                              │   │
│  │                                                      │   │
│  │  5. 所有 key 迁移完成后                               │   │
│  │     双方向全集群广播 slot 归属变更                     │   │
│  │     CLUSTER SETSLOT <slot> NODE <node2>              │   │
│  │                                                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  迁移期间的并发请求处理:                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  客户端请求 key 在正在迁移的 slot 中:                │   │
│  │                                                      │   │
│  │  请求到达 Node1(源):                               │   │
│  │    key 还在 → 直接返回                               │   │
│  │    key 已迁走 → 返回 ASK 重定向到 Node2              │   │
│  │                                                      │   │
│  │  请求到达 Node2(目标):                             │   │
│  │    key 已迁来 → 正常处理                              │   │
│  │    key 还在 Node1 → 返回 MOVED 到 Node1              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  注意:                                                     │
│  ├── 迁移过程中 slot 可以正常读写(数据不会丢)             │
│  ├── ASK 重定向带来短暂延迟                                   │
│  └── 建议在低峰期进行槽迁移                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

6.4 节点替换:Slave 升 Master 后重新部署 ​

┌─────────────────────────────────────────────────────────────┐
│              节点替换操作                                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  场景1:Master 永久故障,Slave 已自动提升                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  [旧M1 宕机]                                        │   │
│  │  [S1 自动提升为新 M1]                                │   │
│  │                                                      │   │
│  │  操作:                                              │   │
│  │  1. 启动一个新的 Redis 实例(作为新 Slave)           │   │
│  │  2. 加入集群并设为新 M1 的 Slave                     │   │
│  │     redis-cli --cluster add-node <新节点IP:6379> \   │   │
│  │               <新M1的IP:6379> \                      │   │
│  │               --cluster-slave                         │   │
│  │  3. 修理/重置旧 M1(可选)                            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  场景2:计划内替换(升级硬件/Redis版本)                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  目标:替换 M1 为新机器,零停机                       │   │
│  │                                                      │   │
│  │  操作:                                              │   │
│  │  1. 在新机器上启动 Redis,作为 M1 的 Slave            │   │
│  │  2. 等待数据同步完成                                  │   │
│  │  3. 手动触发故障转移                                  │   │
│  │     redis-cli -p <Slave端口> CLUSTER FAILOVER        │   │
│  │     → Slave 提升为新 Master,旧 M1 降级              │   │
│  │  4. 移除旧 M1                                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

第7部分:Sentinel vs Cluster 选型决策 ​

7.1 架构对比 ​

┌─────────────────────────────────────────────────────────────┐
│            Sentinel vs Cluster 全景对比                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  维度            │ Sentinel              │ Cluster           │
│  ───────────────┼──────────────────────┼──────────────────  │
│  数据分片        │ 无(所有数据在一起)  │ 有(16384 slots) │
│  写扩展          │ 单主(写QPS上限单机) │ 多主(横向扩展写)│
│  读扩展          │ 多 Slaves 分担        │ 多 Slaves 分担    │
│  内存扩展        │ 受单机限制            │ 横向扩展          │
│  自动故障转移    │ 依赖 Sentinel 集群    │ 内置,不依赖外部 │
│  客户端复杂度    │ 低(直连 Master)     │ 高(需支持路由)  │
│  运维复杂度      │ 低(3~5 个 Sentinel)│ 中(多节点管理)  │
│  事务/Lua脚本    │ 支持完整功能          │ 受限于单 slot     │
│  跨 slot 操作    │ 支持                  │ 需 hash tag       │
│  Pub/Sub         │ 全集群广播            │ 全集群广播(性能差)│
│  最小节点数      │ 3 Sentinel + 2 Redis │ 6 Redis (3M+3S)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

7.2 决策流程图 ​

┌─────────────────────────────────────────────────────────────┐
│              Sentinel vs Cluster 选型决策                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  你的核心需求是什么?                                       │
│     │                                                       │
│     ├── 单机内存够用吗?(如 < 64GB)                        │
│     │   ├── 够用                                            │
│     │   │   ├── 需要高可用吗?                               │
│     │   │   │   ├── 不需要 → 单机 Redis                     │
│     │   │   │   └── 需要                                  │
│     │   │   │       ├── 主从 + Sentinel → 够了             │
│     │   │   │       │   (数据都在一个 Master 上)           │
│     │   │   │       └── 特殊情况:需要多 Master 写         │
│     │   │   │           → Cluster(需要合理的 sharding key)│
│     │   │   │                                               │
│     │   │   └── 需要读写分离 + 自动故障转移 → Sentinel      │
│     │   │                                                   │
│     │   └── 不够用(数据 > 单机内存)                        │
│     │       └── Cluster(唯一解,数据分片)                  │
│     │                                                       │
│     └── 写 QPS 超过单机上限了吗?(如 > 10万/s)             │
│         ├── 没有 → Sentinel 够用                            │
│         └── 超过了 → Cluster(多主分担写入)                │
│                                                             │
│  总结:                                                     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Sentinel 解决:高可用 + 读扩展                       │   │
│  │  Cluster  解决:数据分片 + 写扩展 + 高可用            │   │
│  │                                                      │   │
│  │  大多数中小项目:Sentinel 就够                        │   │
│  │  数据量大/写压力大:选 Cluster                        │   │
│  │  最简单且够用时:不要集群化                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

7.3 典型应用场景举例 ​

┌─────────────────────────────────────────────────────────────┐
│              典型场景选型                                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  场景1:中小型电商(日活 10 万,数据 < 20GB)               │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  架构:Sentinel(1 Master + 2 Slaves + 3 Sentinels) │   │
│  │  理由:数据量小、写压力不大、主从+自动故障转移就够了  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  场景2:大型内容平台(日活 1000 万,数据 > 200GB)          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  架构:Cluster(6 Master + 6 Slave,每对 40GB)      │   │
│  │  理由:数据量远超单机内存,必须分片                    │   │
│  │  按 userId hash,确保用户数据在同一节点               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  场景3:实时排行榜/计数器(写 QPS 20 万)                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  架构:Cluster(多 Master)                           │   │
│  │  理由:单 Master 写不动 20 万 QPS                      │   │
│  │  多 Master 分担写入,每个 Master ~3 万 QPS            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  场景4:企业内部系统(日活 < 1000,数据 < 1GB)             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  架构:单机 Redis                                     │   │
│  │  理由:没有数据量和可用性压力,集群化是过度设计        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

核心总结 ​

总结1:从单机到集群的演进逻辑 ​

单机 → 发现三个瓶颈:
  内存不够 → 分片(Cluster)
  单点故障 → 主从 + 自动故障转移(Sentinel)
  写不动 → 多主分担(Cluster)

Sentinel 解决可用性,Cluster 同时解决可用性 + 扩展性。
1
2
3
4
5
6

总结2:Sentinel 核心机制 ​

SDOWN(主观下线):1 个 Sentinel 认为 Master 挂了
ODOWN(客观下线):≥ Quorum 个 Sentinel 都认为挂了

ODOWN → Leader 选举 → 选新 Master → 通知客户端 → 旧 Master 降级

核心参数:
  down-after-milliseconds: SDOWN 超时
  quorum: ODOWN 所需的票数
  parallel-syncs: 故障转移后同时同步的 Slave 数
1
2
3
4
5
6
7
8
9

总结3:Cluster 核心机制 ​

16384 个 Hash Slots
  → CRC16(key) % 16384 = slot 编号
  → 按 slot 分配 key 到不同 Master
  → 每个 Master 负责一部分 slot

节点间通信:Gossip(PING / PONG / MEET / FAIL)
故障转移:内置,不依赖 Sentinel

客户端路由:
  MOVED = 永久重定向(更新路由表)
  ASK = 临时重定向(槽迁移中,不更新路由表)
1
2
3
4
5
6
7
8
9
10
11

总结4:选型决策 ​

需求方案
高可用(小数据量)Sentinel
数据分片(大数据量)Cluster
高可用 + 读扩展Sentinel
高可用 + 写扩展Cluster
最简单的可靠方案单机 + RDB/AOF 持久化

章节测试 ​

测试1:SDOWN vs ODOWN ​

一个 Sentinel 集群有 5 个 Sentinel 实例,quorum 设为 2。Sentinel-1 发现 Master 超时无响应,标记为 SDOWN。此时需要多少个 Sentinel 同意才能标记 ODOWN 并触发故障转移?

测试2:Hash Slot ​

有一个 key 叫 "product:54321",请写出它在 Redis Cluster 中被路由到哪个 slot 的计算过程(写出公式和计算步骤)。

测试3:MOVED vs ASK ​

客户端收到 MOVED 重定向和 ASK 重定向后,行为上有什么区别?为什么有这种区别?

测试4:Hash Tag ​

为什么在 Redis Cluster 中执行事务(MULTI/EXEC)需要用 Hash Tag?写出一个正确的示例。

测试5:Gossip ​

Redis Cluster 为什么选择 Gossip 协议而不是使用中心化的配置中心(如 ZooKeeper/etcd)?

测试6:故障转移 ​

Sentinel 模式下,Leader Sentinel 从多个 Slave 中选择新 Master 时的优先级顺序是什么?

测试7:选型 ​

一个项目数据量约 200GB,写 QPS 约 5 万/s,读 QPS 约 50 万/s。请设计 Redis 架构并说明理由。

测试8:运维 ​

你有 3 Master + 3 Slave 的 Cluster,想把一个 Master 安全下线(计划内维护),保证数据零丢失,步骤是什么?


参考答案 ​

测试1答案 ​

答案:至少 2 个 Sentinel 同意(≥ quorum=2)。5 个 Sentinel 中至少有 2 个(包括 Sentinel-1 自己)标记 SDOWN 即可达成 ODOWN。

测试2答案 ​

答案:

  1. 计算 CRC16("product:54321") → 假设结果是 48021
  2. slot = 48021 % 16384 = 15253
  3. key 被路由到负责 slot 15253 的 Master 节点

测试3答案 ​

答案:

  • MOVED:永久重定向。客户端应更新本地 slot→node 映射表,后续同一 slot 的请求直接发到正确节点。
  • ASK:临时重定向。slot 正在迁移中,客户端不应更新路由表。发送 ASKING 命令后向目标节点请求,下次同一 slot 的请求仍发原节点。
  • 区别原因:MOVED 表示 slot 归属已确定变更;ASK 表示迁移未完成,归属尚未最终变更。

测试4答案 ​

答案:因为 MULTI/EXEC 中的所有命令必须在同一个节点上原子执行。如果 key 分散在不同 slot,它们落在不同节点上,事务无法跨节点。

正确示例:

MULTI
SET order:{1001}:id 1001
SET order:{1001}:amount 99
INCR order:{1001}:version
EXEC
1
2
3
4
5

花括号 {1001} 确保 3 个 key 的 hash 基于 "1001" 计算,落到同一个 slot。

测试5答案 ​

答案:

  • 无单点故障:没有中心化配置中心,任意节点挂掉不影响集群元数据传播
  • 最终一致性:去中心化的 Gossip 传播满足 Redis 对 AP 的需求
  • 运维简单:不需要额外部署 ZooKeeper/etcd 集群
  • 代价可接受:100 节点规模下,Gossip 消息量很小(每秒约 100 条)

测试6答案 ​

答案:优先级从高到低:

  1. slave-priority(配置值越小越优先,0 表示永远不提升)
  2. 复制偏移量(offset 越大 = 数据越新)
  3. run_id(字典序,作为最终兜底)

测试7答案 ​

答案:推荐 Redis Cluster。

  • 200GB 数据远超单机内存 → 需要数据分片 → Cluster
  • 5 万写 QPS → 单 Master 可能扛不住 → Cluster 多 Master 分担
  • 50 万读 QPS → 每个 Master 挂 2 个 Slave,6 Master 共 12 Slave 分担读
  • 建议:6 Master + 12 Slave,每对 Master-Slave 负责约 200/6≈33GB 数据和约 8k 写 QPS

测试8答案 ​

答案:

  1. 确认该 Master 的 Slave 数据同步正常(检查复制偏移量)
  2. 在 Slave 上执行 CLUSTER FAILOVER 手动触发故障转移
  3. Slave 提升为新 Master,接管原 Master 的 slots
  4. 原 Master 自动降级为 Slave
  5. 等待客户端的路由表全部更新(几秒内)
  6. 安全关闭原 Master 进程
  7. 完成维护后可从集群中移除该节点

相关笔记 ​

  • [[/03-web/07-middleware/01-cache-layer/00-overview]] - 缓存架构全景
  • [[/03-web/07-middleware/01-redis-deep]] - Redis 深入:缓存三大问题
  • [[/03-web/07-middleware/05-cache-strategy]] - 缓存一致性策略
  • [[/03-web/06-databases-and-data-access/05-redis]] - Redis 数据结构基础

下一步学习 ​

  • [ ] 阅读 消息队列基础
  • [ ] 实践:搭建一个 3 Master + 3 Slave 的 Redis Cluster 并验证故障转移

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇6. Redis 分布式锁——从 SETNX 到 Redisson / Redis Distributed Locks from SETNX to Redisson
下一篇8. Redis 高级特性——Stream / PubSub / Module / LLM 应用

持续记录,持续成长

Copyright © Tidenflow