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 架构深度分析——为什么 Redis 能这么快 / Redis Architecture and the Sources of Its Performance ​

📅 创建时间:2026-06-02 🏷️ 标签:#Redis #线程模型 #内存模型 #持久化 #主从复制 #Sentinel #Cluster #RESP协议 📚 前置知识:[[00-redis-overview]](Redis 全景) [[/03-web/06-databases-and-data-access/05-redis]](Redis 数据结构基础) 📚 相关知识:[[03-redis-cache-patterns]](缓存三剑客) [[04-redis-distributed-locks]](分布式锁) [[/03-web/05-backend-engineering/04-distributed-system]](分布式系统)


场景:Redis 为什么这么快,但有时候却"卡住了" ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  日常:Redis 响应时间 0.1ms,每秒处理 10 万 QPS。        │
│                                                             │
│  凌晨 3 点:Redis 突然响应时间飙升到 500ms。             │
│                                                             │
│  排查过程:                                                │
│  1. Redis CPU 不高,内存也够用                             │
│  2. 查看慢查询日志,发现大量 O(N) 命令                     │
│  3. 发现有人在凌晨跑了一个 KEYs * 全表扫描                │
│                                                             │
│  结论:Redis 单线程被一个慢命令阻塞了,所有请求都在等。    │
│                                                             │
│  理解 Redis 的架构,是排查这类问题的前提。                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

第1节:线程模型——Redis 到底是单线程还是多线程 ​

核心结论:Redis 是"单线程 + I/O 多路复用" ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis 6.0 之前的线程模型                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                    主线程(单线程)                   │ │
│  │                                                     │ │
│  │  socket ① ──▶                                     │ │
│  │  socket ② ──▶   I/O 多路复用器(epoll/select)  ▶──▶│ 命令解析器 │──▶│ 命令执行器 │──▶│ 响应写出器 │──▶ socket ①│
│  │  socket ③ ──▶                                     │ │
│  │  ...                                               │ │
│  │                                                     │ │
│  │  所有客户端请求:串行执行,互不阻塞                  │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  为什么用单线程?                                          │
│  1. 避免了锁的开销:多线程共享数据需要加锁,单线程天然无锁 │
│  2. CPU 不是瓶颈:Redis 是内存数据库,瓶颈在内存和网络 IO │
│  3. 简单高效:避免了死锁、上下文切换的复杂性               │
│  4. I/O 多路复用:用一个线程同时处理海量连接               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Redis 6.0 多线程 I/O:突破了网络 IO 的瓶颈 ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis 6.0+ 多线程 I/O 模型                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  主线程(命令执行)                                 │ │
│  │  ←──────────────────────────────────────────────  │ │
│  └─────────────────────────────────────────────────────┘ │
│                          ▲                                   │
│            ┌─────────────┴─────────────┐                     │
│            │                           │                      │
│    ┌───────┴───────┐         ┌───────┴───────┐           │
│    │  I/O 线程 1   │         │  I/O 线程 2   │    ← 读取和写出由多线程并行 │
│    └───────┬───────┘         └───────┬───────┘           │
│            │                           │                      │
│            └─────────────┬─────────────┘                     │
│                          ▼                                    │
│  ┌─────────────────────────────────────────────────────┐ │
│  │              socket 队列(请求分发)                  │ │
│  │                                                     │ │
│  │  socket ① ──▶                                     │ │
│  │  socket ② ──▶   epoll(多路复用)                 │ │
│  │  socket ③ ──▶   接收请求 ──▶ 放入队列             │ │
│  │  ...                                               │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  关键变化:                                                │
│  • 命令执行仍然是单线程(保证了原子性)                    │
│  • 网络 IO(读取请求、写出响应)由多线程并行处理           │
│  • 吞吐量提升 2-3 倍                                      │
│                                                             │
│  配置:io-threads-do-readings yes                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

哪些操作是"伪单线程"(真正多线程执行) ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis 中的多线程操作                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  以下操作会fork 子进程,在子进程中执行:                     │
│                                                             │
│  1. RDB 快照生成(BGSAVE)                                │
│     → fork() 一个子进程,专门做全量快照                     │
│     → 父进程继续处理请求,不阻塞                           │
│                                                             │
│  2. AOF 文件重写(BGREWRITEAOF)                          │
│     → fork() 子进程,把内存数据重写成 AOF                  │
│                                                             │
│  3. 混合持久化(AOF 重写时嵌入 RDB)                       │
│     → 同上                                                   │
│                                                             │
│  ⚠️ fork() 在数据量大时(10GB+)会有短暂阻塞               │
│  ⚠️ fork() 的时间与 Redis 占用的内存成正比                 │
│                                                             │
│  一句话总结:                                               │
│  Redis 单线程指"命令执行"单线程,                         │
│  网络 IO(6.0+)和后台持久化是并行的。                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

第2节:内存模型——Redis 用了什么数据结构存数据 ​

Redis 内存布局全景图 ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis 内存布局                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Redis 进程内存                                            │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ 代码段 + 共享内存 + 栈 + 堆                          │ │
│  │                                                     │ │
│  │  ┌─────────────────────────────────────────────┐ │ │
│  │  │        used_memory(数据实际占用)             │ │ │
│  │  │                                             │ │ │
│  │  │  String:SDS(简单动态字符串)               │ │ │
│  │  │  Hash:dictht(字典 + 拉链法解决冲突)       │ │ │
│  │  │  List:quicklist(压缩列表 + 双向链表)      │ │ │
│  │  │  Set:intset(小整数)/ hashtable(大集合)  │ │ │
│  │  │  ZSet:skiplist(跳表)+ dict(反向查分)    │ │ │
│  │  │                                             │ │ │
│  │  └─────────────────────────────────────────────┘ │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  used_memory 只算数据区,不算进程自身占用。                 │
│  监控时用 used_memory_human 更直观。                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

String 的底层:SDS(简单动态字符串) ​

┌─────────────────────────────────────────────────────────────┐
│                 SDS vs C 字符串                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  C 字符串的问题:                                           │
│  • 拼接时需要 realloc,可能触发多次内存分配                 │
│  • 不记录长度,每次 strlen() 要遍历整个数组                 │
│  • 二进制数据(含 \0)会截断                               │
│                                                             │
│  SDS 的改进(Redis 自己实现的字符串):                     │
│                                                             │
│  struct sdshdr {                                            │
│      int len;        // 已用长度(记录了!不用遍历)        │
│      int free;       // 剩余空间(预分配,减少 realloc)   │
│      char buf[];     // 实际存储                           │
│  };                                                         │
│                                                             │
│  SDS 优势:                                                │
│  • O(1) 获取长度(sdslen)                                │
│  • 预分配策略:空间不够时多分配一些,避免频繁 realloc     │
│  • 二进制安全:记录了长度,不怕 \0                        │
│  • 兼容 C 字符串函数(buf 以 \0 结尾)                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

ZSet 的底层:跳表(SkipList)+ 哈希表 ​

┌─────────────────────────────────────────────────────────────┐
│                 跳表——有序集合的核心数据结构                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  为什么不用平衡树(B-Tree)?                               │
│  • 平衡树实现复杂(红黑树插入/删除要旋转)                 │
│  • 跳表实现简单,只靠随机层数决定平衡                     │
│  • 跳表区间查询(ZRANGE)比 B-Tree 更高效                  │
│                                                             │
│  跳表原理(以积分 100, 60, 80, 40, 90, 30 为例):         │
│                                                             │
│  Level 2: ──────▶ [30] ──────▶ [60] ──────▶ [90] ────▶ NULL
│            (head)       ▲                       ▲          │
│                         │                       │          │
│  Level 1: ────▶ [30] ──▶│──▶ [40] ──▶ [60] ──▶│──▶ [80] ──▶│──▶ [90] ──▶ NULL
│            (head)                     ▲                       ▲
│                                      │                       │
│  Level 0: [30] ─▶ [40] ─▶ [60] ─▶ [80] ─▶ [90] ─▶ NULL   │
│                                                             │
│  查询 80:从 Level 2 出发,30 → 90(太大)→ 下沉 → 找到 80 │
│                                                             │
│  时间复杂度:                                              │
│  • 查:O(log N)(最多走 log N 层,每层最多走 1 个节点)   │
│  • 插:O(log N)(查 + 随机决定层数 + 插入)              │
│  • 范围查(ZRANGE):O(log N + M)(M 为结果数量)         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

第3节:内存淘汰策略——内存满了怎么办 ​

八种淘汰策略全景图 ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis 8 种内存淘汰策略                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  在 redis.conf 中配置:maxmemory-policy xxx                  │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ 不淘汰(超过 maxmemory 后报错)                      │ │
│  │  noeviction(默认):写操作报错 OOM                   │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ allkeys-*(对所有 key 生效)                        │ │
│  │                                                     │ │
│  │  allkeys-lru:       删除最近最少使用的 key ⭐常用  │ │
│  │  allkeys-lfu:       删除使用频率最低的 key         │ │
│  │  allkeys-random:    随机删除                       │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ volatile-*(只对设置了过期时间的 key 生效)          │ │
│  │                                                     │ │
│  │  volatile-lru:  删除最近最少使用的 key(有过期的) │ │
│  │  volatile-lfu:  删除使用频率最低的 key(有过期的) │ │
│  │  volatile-random:随机删除(有过期的)               │ │
│  │  volatile-ttl:   删除即将过期的 key(TTL 最小的)  │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  选型建议:                                                │
│  • 缓存场景 → allkeys-lru(最常用)                       │
│  • 冷热分明 → allkeys-lfu                                 │
│  • 不想删有过期时间的数据 → allkeys-random                 │
│  • 想保留热点数据,只删快过期的 → volatile-ttl            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

LRU / LFU 的底层实现 ​

┌─────────────────────────────────────────────────────────────┐
│                 LRU 和 LFU 的实现原理                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  LRU(Least Recently Used):                               │
│  • Redis Object 中记录 last_accessed_time                  │
│  • 淘汰时找距今最久未被访问的                               │
│  • 精确 LRU 需要维护双向链表 → O(N) 扫描慢                │
│                                                             │
│  • Redis 的优化:采样 N 个(默认 5),淘汰样本中最旧的     │
│  • 配置:maxmemory-samples 10(采样越多越精确,越慢)      │
│                                                             │
│  LFU(Least Frequently Used):                             │
│  • 每个 Object 记录:counter(使用次数)+ decr_time(递减时间)│
│  • 访问一次 → counter +1,但会用概率衰减(防止"冷数据      │
│    突然变热"后长期占据热点位置)                           │
│  • 更适合长期热点的场景                                    │
│                                                             │
│  一句话:                                                  │
│  LRU 看"什么时候用过",LFU 看"用过多少次"。              │
│  长期稳定热点的场景 LFU 更优,突发热点的场景 LRU 更优。   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

第4节:持久化策略——Redis 重启后数据怎么恢复 ​

RDB:定时快照 ​

┌─────────────────────────────────────────────────────────────┐
│                 RDB 快照原理                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  触发方式:                                                │
│  1. BGSAVE:后台异步 fork 子进程生成 .rdb 文件            │
│  2. SAVE:   同步执行,阻塞所有请求(生产环境禁用)       │
│  3. 自动触发:根据配置规则自动执行                         │
│                                                             │
│  自动触发配置(redis.conf):                               │
│  save 900 1      # 900 秒内 ≥1 次写 → 触发 BGSAVE       │
│  save 300 100     # 300 秒内 ≥100 次写 → 触发 BGSAVE     │
│  save 60 10000    # 60 秒内 ≥10000 次写 → 触发 BGSAVE    │
│                                                             │
│  fork() 子进程的原理:                                      │
│  • fork() 瞬间复制父进程的整个地址空间(Copy-On-Write)   │
│  • 父子进程共享物理内存,只有写入时才复制(节省内存)     │
│  • 子进程遍历内存,序列化写入 .rdb 文件                    │
│  • 父进程继续处理请求,不阻塞                             │
│                                                             │
│  优点:文件紧凑(全量数据),恢复快                        │
│  缺点:可能丢失最后一次 BGSAVE 之后的数据                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

AOF:写命令日志 ​

┌─────────────────────────────────────────────────────────────┐
│                 AOF(Append Only File)原理                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  写操作流程:                                              │
│  用户执行 SET name "Alice"                                 │
│  → Redis 把这个命令追加写入 .aof 文件                       │
│  → 下次启动时,Redis 读取 .aof 文件并重放所有命令          │
│                                                             │
│  三种同步策略(redis.conf):                               │
│                                                             │
│  appendfsync always                                       │
│  → 每次写操作都 fsync(同步写入磁盘)                      │
│  → 最安全,但最慢(每次写都等磁盘)                        │
│  → SSD 下约 10 万 QPS,机械硬盘约 1 万 QPS               │
│                                                             │
│  appendfsync everysec(默认,推荐)                        │
│  → 每秒执行一次 fsync                                     │
│  → 最多丢失 1 秒数据,平衡了安全和性能                    │
│                                                             │
│  appendfsync no                                            │
│  → 交给操作系统决定何时 fsync                             │
│  → 最快,但可能丢失几秒甚至几十秒数据                      │
│                                                             │
│  AOF 重写(BGREWRITEAOF):                                │
│  • .aof 文件会越来越大(同一个 key 被 SET 了 1 万次)       │
│  • 重写:fork 子进程,把内存中的数据写成新的 .aof          │
│  • 新文件只有最终状态,没有历史中间操作                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

混合持久化(RDB + AOF,推荐生产使用) ​

┌─────────────────────────────────────────────────────────────┐
│                 混合持久化——取长补短                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  配置:aof-use-rdb-preamble yes                            │
│                                                             │
│  重写 AOF 时:                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  AOF 文件结构:                                     │ │
│  │                                                     │ │
│  │  [RDB 二进制头]  [AOF 命令追加]                    │ │
│  │       ↑                ↑                            │ │
│  │   全量数据           增量修改                       │ │
│  │  (快速恢复)        (不丢数据)                    │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  启动时恢复流程:                                          │
│  1. 先加载 RDB 部分 → 快速恢复到大部分数据               │
│  2. 再重放 AOF 命令 → 补上 RDB 之后的增量修改             │
│                                                             │
│  优点:                                                   │
│  • 启动恢复比纯 AOF 快(不用重放所有历史命令)           │
│  • 比纯 RDB 丢的数据少(保留了增量部分)                 │
│                                                             │
│  生产环境推荐配置:                                        │
│  • 混合持久化开启(yes)                                  │
│  • AOF 策略:everysec                                     │
│  • RDB 定时快照:作为冷备,丢失不超过 5 分钟数据        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

三种持久化策略对比 ​

┌─────────────────────────────────────────────────────────────┐
│              RDB / AOF / 混合持久化对比                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  │ 策略      │ 数据安全性 │ 恢复速度 │ 文件大小 │ 性能影响 │
│  ├───────────┼────────────┼──────────┼──────────┼──────────┤
│  │ RDB       │  低(可能丢 │  快      │  小     │  低     │
│  │           │  失 1 次快照 │  (全量)│  (紧凑)│  (fork 短暂)│
│  │           │  后的数据)  │          │          │          │
│  ├───────────┼────────────┼──────────┼──────────┼──────────┤
│  │ AOF always │  最高      │  慢      │  大     │  高     │
│  │           │  (不丢)   │  (重放)│  (累积)│  (每次IO)│
│  ├───────────┼────────────┼──────────┼──────────┼──────────┤
│  │ AOF everysec│  中等    │  中等    │  中等   │  中等   │
│  │           │  (丢 1 秒)│          │          │          │
│  ├───────────┼────────────┼──────────┼──────────┼──────────┤
│  │ 混合持久化│  高        │  快      │  中等   │  低     │
│  │(推荐)   │  (丢增量  │  (RDB) │          │          │
│  │           │  部分)    │          │          │          │
│                                                             │
│  生产推荐:混合持久化 + AOF everysec + RDB 定时冷备         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

第5节:高可用架构——主从复制 + Sentinel + Cluster ​

主从复制:读写分离 ​

┌─────────────────────────────────────────────────────────────┐
│                 主从复制架构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌────────────┐    同步    ┌────────────┐                  │
│  │ Master     │ ─────────▶ │ Slave 1    │                  │
│  │ (主节点)   │            │ (读从节点) │                  │
│  └────────────┘            └────────────┘                  │
│         │                                                     │
│         │ 同步                                                │
│         ▼                                                     │
│  ┌────────────┐                                             │
│  │ Slave 2    │                                             │
│  │ (读从节点) │                                             │
│  └────────────┘                                             │
│                                                             │
│  复制过程:                                                 │
│  1. Slave 向 Master 发送 PSYNC ? -1(请求全量同步)         │
│  2. Master 执行 BGSAVE,生成 RDB 快照                      │
│  3. Master 把 RDB 发送给 Slave                             │
│  4. Master 在此期间的写命令存入 repl_backlog_buffer         │
│  5. Master 把 backlog 中的增量命令发给 Slave                │
│  6. 之后 Master 每条写命令都异步发给 Slave(命令传播)     │
│                                                             │
│  全量同步(PSYNC ? -1):                                  │
│  → 首次连接,Master 把整个数据发过去                       │
│  → 数据量大时耗时较长                                       │
│                                                             │
│  增量同步(PSYNC {runId} {offset}):                      │
│  → 断线重连时,Slave 带上 runId 和 offset                  │
│  → 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

主从复制的三个坑 ​

┌─────────────────────────────────────────────────────────────┐
│                 主从复制的常见问题                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  坑 1:主从延迟                                           │
│  • 写 Master → 异步同步到 Slave,有延迟(毫秒到秒级)       │
│  • 读写分离时:刚写入就读取,可能读到从节点的旧数据         │
│  • 解法:强制读主(强一致场景);接受短暂不一致             │
│                                                             │
│  坑 2:脑裂问题(Split Brain)                             │
│  • Master 和 Slave 网络断开,各自认为对方挂了               │
│  • 客户端继续写旧的 Master(新 Master 已选出)               │
│  • 网络恢复后,旧的 Master 降为 Slave,数据被覆盖           │
│  • 解法:Redis Cluster + 投票机制;配置 min-slaves-to-write │
│                                                             │
│  坑 3:复制风暴                                           │
│  • Master 重启后,所有 Slave 同时请求全量同步               │
│  • Master 网络被打满                                       │
│  • 解法:依次重启 Slave;使用 slot 迁移,分批同步           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Sentinel 哨兵:高可用自动故障转移 ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis Sentinel 哨兵架构                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│              应用客户端 ──(询问主节点地址)──┐             │
│                     ▲                              │         │
│                     │                              ▼         │
│         ┌───────────┴───────────┐         ┌────────────┐  │
│         │  Sentinel 1          │         │   Master   │  │
│         │  (Leader)            │◀───────▶│ (主节点)   │  │
│         │  Sentinel 2          │         └─────┬──────┘  │
│         │  Sentinel 3          │               │           │
│         └───────────┬───────────┘        ┌─────┴──────┐  │
│                     │                   │            │      │
│                     ▼                   ▼            ▼      │
│              监控/通知/选主      ┌─────────┐  ┌─────────┐  │
│                                   │ Slave 1 │  │ Slave 2 │  │
│                                   └─────────┘  └─────────┘  │
│                                                             │
│  Sentinel 的三大职责:                                       │
│  1. 监控(Monitoring):持续检查 Master 和 Slave 是否存活   │
│  2. 通知(Notification):节点 down 机时通知客户端        │
│  3. 自动故障转移:Master 挂了 → 投票选主 → 从升主        │
│                                                             │
│  选主过程:                                                │
│  主观下线(SDOWN)→ 客观下线(ODOWN,quorum 确认)→       │
│  Leader 选举(Sentinel 间投票,Raft 风格)→ 新 Master 上线 │
│                                                             │
│  配置推荐:至少 3 个 Sentinel 节点(奇数,防止脑裂)        │
│  sentinel monitor mymaster 127.0.0.1 6379 2(quorum=2)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

Cluster 槽分片:水平扩展 ​

┌─────────────────────────────────────────────────────────────┐
│                 Redis Cluster 槽分片架构                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  16384 个 slot,均匀分布在多个主节点上:                    │
│                                                             │
│         客户端                                             │
│            │                                               │
│            │(MOVED 重定向 / ASK 重定向)                   │
│            ▼                                               │
│  ┌────────────────────────────────────────────────────┐  │
│  │              Redis Cluster                         │  │
│  │                                                    │  │
│  │  Slot 0-5460  ──────────▶  Node-1 (Master)      │  │
│  │                                  │  ↕ 副本同步    │  │
│  │                                  │  Node-1-Slave  │  │
│  │  Slot 5461-10922 ──────────▶  Node-2 (Master)    │  │
│  │                                  │  ↕ 副本同步    │  │
│  │                                  │  Node-2-Slave  │  │
│  │  Slot 10923-16383 ──────────▶  Node-3 (Master)   │  │
│  │                                  │  ↕ 副本同步    │  │
│  │                                  │  Node-3-Slave  │  │
│  └────────────────────────────────────────────────────┘  │
│                                                             │
│  key 的 slot 计算:crc16(key) % 16384                      │
│                                                             │
│  客户端路由策略:                                          │
│  • MOVED 重定向:slot 已迁移,客户端更新本地路由表         │
│  • ASK 重定向:slot 迁移中,客户端继续访问旧节点           │
│                                                             │
│  Hash Tag(让相关数据落在同一节点):                      │
│  • key = "user:{1001}:profile" 和 "user:{1001}:orders"   │
│  • slot 计算只看 {} 中的部分 {1001},保证同节点           │
│                                                             │
│  故障转移:主节点 down → 从节点自动升主 → cluster 重新选举│
│                                                             │
│  生产环境建议:至少 3 主 3 从,每主至少 1 从               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

Sentinel vs Cluster:怎么选 ​

┌─────────────────────────────────────────────────────────────┐
│              Sentinel 和 Cluster 的核心区别                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  │ 维度       │ Sentinel             │ Cluster              │
│  ├────────────┼──────────────────────┼──────────────────────┤
│  │ 定位       │ 高可用(故障转移)    │ 水平扩展(分片)      │
│  ├────────────┼──────────────────────┼──────────────────────┤
│  │ 数据分片   │ 无(所有节点存相同数据)│ 有(16384 slot 分片)│
│  ├────────────┼──────────────────────┼──────────────────────┤
│  │ 写能力     │ 主节点单点写          │ 多主节点写           │
│  ├────────────┼──────────────────────┼──────────────────────┤
│  │ 内存上限   │ 单机内存上限          │ 集群总内存 = N × 单机│
│  ├────────────┼──────────────────────┼──────────────────────┤
│  │ 复杂度     │ 低                   │ 高                   │
│  ├────────────┼──────────────────────┼──────────────────────┤
│  │ 多 key 操作│ 支持                 │ 需要 hash tag 同节点  │
│                                                             │
│  选型建议:                                                │
│  • 数据量 < 50GB,读多写少 → Sentinel(主从复制)           │
│  • 数据量 > 50GB,或需要水平写扩展 → Cluster                │
│  • 或者:数据量小但要求高可用 → Sentinel                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

第6节:RESP 协议——Redis 和客户端怎么通信 ​

┌─────────────────────────────────────────────────────────────┐
│                 RESP(Redis Serialization Protocol)           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Redis 客户端和服务端之间用一种简单的文本协议通信:          │
│  每行第一个字节表示类型:                                   │
│                                                             │
│  + 表示简单字符串(状态回复):                             │
│  +OK\r\n                                                   │
│                                                             │
│  - 表示错误:                                              │
│  -ERR unknown command 'FOO'\r\n                            │
│                                                             │
│  : 表示整数:                                              │
│  :1000\r\n                                                 │
│                                                             │
│  $ 表示 Bulk 字符串(二进制安全):                         │
│  $5\r\nhello\r\n      → 5 字节的字符串 "hello"             │
│  $-1\r\n         → nil(空值)                            │
│                                                             │
│  * 表示数组:                                              │
│  *3\r\n                                                    │
│  $3\r\nSET\r\n                                             │
│  $4\r\nname\r\n                                            │
│  $5\r\nAlice\r\n                                            │
│  → 执行 SET name Alice                                     │
│                                                             │
│  telnet 127.0.0.1 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
26
27
28
29
30

升华:Redis 架构的核心设计思想 ​

┌─────────────────────────────────────────────────────────────┐
│              Redis 快的原因:每个选择都是工程 trade-off         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  为什么单线程?                                            │
│  → 避免锁开销 + 内存数据库瓶颈不在 CPU → 简单即高效       │
│                                                             │
│  为什么用跳表而不是红黑树?                               │
│  → 实现简单 + 范围查询友好 + 插入/删除只需局部修改        │
│                                                             │
│  为什么用 SDS 而不是 C 字符串?                            │
│  → 二进制安全 + O(1) 长度获取 + 减少内存重分配            │
│                                                             │
│  为什么 AOF 默认是 everysec 而不是 always?                 │
│  → 平衡数据安全和写入性能,接受最多丢失 1 秒               │
│                                                             │
│  为什么用 Copy-On-Write 做 RDB?                          │
│  → 父进程无阻塞 + 物理内存共享节省空间                   │
│                                                             │
│  工程哲学:                                               │
│  Redis 的每个设计都是为了"在保证足够安全的前提下,        │
│  用最简单的方式实现最高性能"。                           │
│  理解了这个哲学,你就能预判 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

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ redis.conf 中各个配置项的具体含义
✅ 不同语言的 RESP 协议解析库
✅ Redis 不同版本间的特性差异

必须理解:
🔴 Redis 单线程指"命令执行"单线程,6.0+ 网络 IO 是多线程的
🔴 为什么 Redis 用跳表而不是红黑树(范围查询 vs 实现复杂度)
🔴 RDB / AOF / 混合持久化各自的优缺点和适用场景
🔴 8 种内存淘汰策略的分类逻辑(allkeys vs volatile / LRU vs LFU vs random vs TTL)
🔴 主从复制的全量同步和增量同步的触发条件
🔴 Sentinel 和 Cluster 的定位区别(高可用 vs 水平扩展)
🔴 脑裂问题的原因和缓解方案
1
2
3
4
5
6
7
8
9
10
11
12
13

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇8. Redis 高级特性——Stream / PubSub / Module / LLM 应用
下一篇10. Redis 全景——为什么你的系统需要一个缓存层 / The Redis Landscape and Why Systems Need a Cache Layer

持续记录,持续成长

Copyright © Tidenflow