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节:线程模型——Redis 到底是单线程还是多线程
核心结论:Redis 是"单线程 + I/O 多路复用"
┌─────────────────────────────────────────────────────────────┐
│ Redis 6.0 之前的线程模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 主线程(单线程) │ │
│ │ │ │
│ │ socket ① ──▶ │ │
│ │ socket ② ──▶ I/O 多路复用器(epoll/select) ▶──▶│ 命令解析器 │──▶│ 命令执行器 │──▶│ 响应写出器 │──▶ socket ①│
│ │ socket ③ ──▶ │ │
│ │ ... │ │
│ │ │ │
│ │ 所有客户端请求:串行执行,互不阻塞 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 为什么用单线程? │
│ 1. 避免了锁的开销:多线程共享数据需要加锁,单线程天然无锁 │
│ 2. CPU 不是瓶颈:Redis 是内存数据库,瓶颈在内存和网络 IO │
│ 3. 简单高效:避免了死锁、上下文切换的复杂性 │
│ 4. I/O 多路复用:用一个线程同时处理海量连接 │
│ │
└─────────────────────────────────────────────────────────────┘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 │
│ │
└─────────────────────────────────────────────────────────────┘哪些操作是"伪单线程"(真正多线程执行)
┌─────────────────────────────────────────────────────────────┐
│ Redis 中的多线程操作 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 以下操作会fork 子进程,在子进程中执行: │
│ │
│ 1. RDB 快照生成(BGSAVE) │
│ → fork() 一个子进程,专门做全量快照 │
│ → 父进程继续处理请求,不阻塞 │
│ │
│ 2. AOF 文件重写(BGREWRITEAOF) │
│ → fork() 子进程,把内存数据重写成 AOF │
│ │
│ 3. 混合持久化(AOF 重写时嵌入 RDB) │
│ → 同上 │
│ │
│ ⚠️ fork() 在数据量大时(10GB+)会有短暂阻塞 │
│ ⚠️ fork() 的时间与 Redis 占用的内存成正比 │
│ │
│ 一句话总结: │
│ Redis 单线程指"命令执行"单线程, │
│ 网络 IO(6.0+)和后台持久化是并行的。 │
│ │
└─────────────────────────────────────────────────────────────┘第2节:内存模型——Redis 用了什么数据结构存数据
Redis 内存布局全景图
┌─────────────────────────────────────────────────────────────┐
│ Redis 内存布局 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Redis 进程内存 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 代码段 + 共享内存 + 栈 + 堆 │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ used_memory(数据实际占用) │ │ │
│ │ │ │ │ │
│ │ │ String:SDS(简单动态字符串) │ │ │
│ │ │ Hash:dictht(字典 + 拉链法解决冲突) │ │ │
│ │ │ List:quicklist(压缩列表 + 双向链表) │ │ │
│ │ │ Set:intset(小整数)/ hashtable(大集合) │ │ │
│ │ │ ZSet:skiplist(跳表)+ dict(反向查分) │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ used_memory 只算数据区,不算进程自身占用。 │
│ 监控时用 used_memory_human 更直观。 │
│ │
└─────────────────────────────────────────────────────────────┘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 结尾) │
│ │
└─────────────────────────────────────────────────────────────┘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 为结果数量) │
│ │
└─────────────────────────────────────────────────────────────┘第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 │
│ │
└─────────────────────────────────────────────────────────────┘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 更优。 │
│ │
└─────────────────────────────────────────────────────────────┘第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 之后的数据 │
│ │
└─────────────────────────────────────────────────────────────┘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 │
│ • 新文件只有最终状态,没有历史中间操作 │
│ │
└─────────────────────────────────────────────────────────────┘混合持久化(RDB + AOF,推荐生产使用)
┌─────────────────────────────────────────────────────────────┐
│ 混合持久化——取长补短 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 配置:aof-use-rdb-preamble yes │
│ │
│ 重写 AOF 时: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ AOF 文件结构: │ │
│ │ │ │
│ │ [RDB 二进制头] [AOF 命令追加] │ │
│ │ ↑ ↑ │ │
│ │ 全量数据 增量修改 │ │
│ │ (快速恢复) (不丢数据) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 启动时恢复流程: │
│ 1. 先加载 RDB 部分 → 快速恢复到大部分数据 │
│ 2. 再重放 AOF 命令 → 补上 RDB 之后的增量修改 │
│ │
│ 优点: │
│ • 启动恢复比纯 AOF 快(不用重放所有历史命令) │
│ • 比纯 RDB 丢的数据少(保留了增量部分) │
│ │
│ 生产环境推荐配置: │
│ • 混合持久化开启(yes) │
│ • AOF 策略:everysec │
│ • RDB 定时快照:作为冷备,丢失不超过 5 分钟数据 │
│ │
└─────────────────────────────────────────────────────────────┘三种持久化策略对比
┌─────────────────────────────────────────────────────────────┐
│ RDB / AOF / 混合持久化对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 策略 │ 数据安全性 │ 恢复速度 │ 文件大小 │ 性能影响 │
│ ├───────────┼────────────┼──────────┼──────────┼──────────┤
│ │ RDB │ 低(可能丢 │ 快 │ 小 │ 低 │
│ │ │ 失 1 次快照 │ (全量)│ (紧凑)│ (fork 短暂)│
│ │ │ 后的数据) │ │ │ │
│ ├───────────┼────────────┼──────────┼──────────┼──────────┤
│ │ AOF always │ 最高 │ 慢 │ 大 │ 高 │
│ │ │ (不丢) │ (重放)│ (累积)│ (每次IO)│
│ ├───────────┼────────────┼──────────┼──────────┼──────────┤
│ │ AOF everysec│ 中等 │ 中等 │ 中等 │ 中等 │
│ │ │ (丢 1 秒)│ │ │ │
│ ├───────────┼────────────┼──────────┼──────────┼──────────┤
│ │ 混合持久化│ 高 │ 快 │ 中等 │ 低 │
│ │(推荐) │ (丢增量 │ (RDB) │ │ │
│ │ │ 部分) │ │ │ │
│ │
│ 生产推荐:混合持久化 + AOF everysec + RDB 定时冷备 │
│ │
└─────────────────────────────────────────────────────────────┘第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:主从延迟 │
│ • 写 Master → 异步同步到 Slave,有延迟(毫秒到秒级) │
│ • 读写分离时:刚写入就读取,可能读到从节点的旧数据 │
│ • 解法:强制读主(强一致场景);接受短暂不一致 │
│ │
│ 坑 2:脑裂问题(Split Brain) │
│ • Master 和 Slave 网络断开,各自认为对方挂了 │
│ • 客户端继续写旧的 Master(新 Master 已选出) │
│ • 网络恢复后,旧的 Master 降为 Slave,数据被覆盖 │
│ • 解法:Redis Cluster + 投票机制;配置 min-slaves-to-write │
│ │
│ 坑 3:复制风暴 │
│ • Master 重启后,所有 Slave 同时请求全量同步 │
│ • Master 网络被打满 │
│ • 解法:依次重启 Slave;使用 slot 迁移,分批同步 │
│ │
└─────────────────────────────────────────────────────────────┘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) │
│ │
└─────────────────────────────────────────────────────────────┘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 从 │
│ │
└─────────────────────────────────────────────────────────────┘Sentinel vs Cluster:怎么选
┌─────────────────────────────────────────────────────────────┐
│ Sentinel 和 Cluster 的核心区别 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 维度 │ Sentinel │ Cluster │
│ ├────────────┼──────────────────────┼──────────────────────┤
│ │ 定位 │ 高可用(故障转移) │ 水平扩展(分片) │
│ ├────────────┼──────────────────────┼──────────────────────┤
│ │ 数据分片 │ 无(所有节点存相同数据)│ 有(16384 slot 分片)│
│ ├────────────┼──────────────────────┼──────────────────────┤
│ │ 写能力 │ 主节点单点写 │ 多主节点写 │
│ ├────────────┼──────────────────────┼──────────────────────┤
│ │ 内存上限 │ 单机内存上限 │ 集群总内存 = N × 单机│
│ ├────────────┼──────────────────────┼──────────────────────┤
│ │ 复杂度 │ 低 │ 高 │
│ ├────────────┼──────────────────────┼──────────────────────┤
│ │ 多 key 操作│ 支持 │ 需要 hash tag 同节点 │
│ │
│ 选型建议: │
│ • 数据量 < 50GB,读多写少 → Sentinel(主从复制) │
│ • 数据量 > 50GB,或需要水平写扩展 → Cluster │
│ • 或者:数据量小但要求高可用 → Sentinel │
│ │
└─────────────────────────────────────────────────────────────┘第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 后可以直接输入命令测试 │
│ │
└─────────────────────────────────────────────────────────────┘升华:Redis 架构的核心设计思想
┌─────────────────────────────────────────────────────────────┐
│ Redis 快的原因:每个选择都是工程 trade-off │
├─────────────────────────────────────────────────────────────┤
│ │
│ 为什么单线程? │
│ → 避免锁开销 + 内存数据库瓶颈不在 CPU → 简单即高效 │
│ │
│ 为什么用跳表而不是红黑树? │
│ → 实现简单 + 范围查询友好 + 插入/删除只需局部修改 │
│ │
│ 为什么用 SDS 而不是 C 字符串? │
│ → 二进制安全 + O(1) 长度获取 + 减少内存重分配 │
│ │
│ 为什么 AOF 默认是 everysec 而不是 always? │
│ → 平衡数据安全和写入性能,接受最多丢失 1 秒 │
│ │
│ 为什么用 Copy-On-Write 做 RDB? │
│ → 父进程无阻塞 + 物理内存共享节省空间 │
│ │
│ 工程哲学: │
│ Redis 的每个设计都是为了"在保证足够安全的前提下, │
│ 用最简单的方式实现最高性能"。 │
│ 理解了这个哲学,你就能预判 Redis 在各种场景下的行为。 │
│ │
└─────────────────────────────────────────────────────────────┘"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 水平扩展)
🔴 脑裂问题的原因和缓解方案学习状态:🟡 开始学习