Redis 速查手册

速查向 · 数据结构与底层编码、持久化、高可用、缓存三大问题、分布式锁、常见问题解析

1. 概述与基础

1.1 Redis 为什么快

Q:Redis 单线程为什么还这么快? 纯内存操作(无磁盘 IO);单线程无锁无上下文切换与竞争开销;IO 多路复用(epoll)单线程管理大量连接;高效数据结构(跳表、压缩列表、渐进式 rehash)。
注意:Redis 6.0 之后网络 IO 是多线程的,但命令执行仍是单线程。
Q:单线程的瓶颈在哪? CPU 不是瓶颈,网络带宽和内存才是。单线程下,大 key、慢命令(keys *、hgetall 大 hash)会阻塞所有请求。

1.2 与 Memcached 对比

1.3 应用场景

场景说明
缓存热点数据、页面缓存、会话(Session)
计数器 / 限流INCR、滑动窗口、令牌桶
排行榜ZSet 有序集合
分布式锁SET NX EX + Lua 释放
消息队列List(LPUSH/BRPOP)、Stream
社交关系Set 交集并集(共同关注)
BitMap / 签到SETBIT、BITCOUNT,签到统计、布隆过滤器

2. 数据类型与底层编码

2.1 五大基础类型

类型特点典型用途
String二进制安全,可存任意数据缓存对象、计数器、分布式锁
List双向链表,两端操作 O(1)消息队列、最新列表
Hashfield-value 映射存对象、购物车
Set无序去重集合标签、共同好友、去重
ZSet有序集合,按 score 排序排行榜、延迟队列

2.2 底层编码(高频考点)

类型编码转换条件
Stringint / embstr / raw整数值→int;≤44 字节→embstr;否则 raw
Listlistpack / quicklist3.2+ 统一用 quicklist(listpack 节点组成的双向链表)
Hashlistpack / hashtable元素 < 512 且值 < 64 字节→listpack,否则 hashtable
Setintset / listpack / hashtable全整数且元素少→intset;非整数小集合→listpack
ZSetlistpack / skiplist元素 < 128 且值 < 64 字节→listpack,否则 skiplist
阈值可配:hash-max-listpack-entries、zset-max-listpack-entries 等(旧版本叫 *-ziplist-*)。转换单向不可逆。

2.3 跳跃表 skiplist

2.4 特殊类型

3. 持久化

3.1 RDB vs AOF

维度RDBAOF
内容某一时刻的数据快照(二进制)记录的写命令日志
恢复速度快慢(需重放命令)
文件体积小(压缩)大
数据安全差(可能丢最后一次快照后的数据)好(取决于 appendfsync)
对性能影响fork 子进程,有瞬时阻塞写盘有 IO 开销

RDB 触发:save(阻塞)/ bgsave(fork 子进程,写时复制 COW)。

AOF 刷盘策略(appendfsync):

AOF 重写:命令越来越多导致文件膨胀,用 bgrewriteaof fork 子进程压缩重写为最小命令集。重写期间新命令写入 AOF 重写缓冲区,完成后追加。

3.2 混合持久化

Redis 4.0+ 支持 aof-use-rdb-preamble yes:AOF 文件前半部分是 RDB 格式的全量快照,后半部分是增量命令。重启恢复又快又安全。

选择:纯缓存可只用 RDB;要求数据可靠用 AOF everysec 或混合持久化。

4. 高可用与集群

4.1 主从复制

4.2 哨兵 Sentinel

4.3 Cluster 分片集群

5. 缓存三大问题(必考)

5.1 缓存穿透

Q:什么是缓存穿透?怎么解决? 查询不存在的数据,缓存和 DB 都没有,请求每次都打到 DB(常见于恶意攻击)。
解决:① 缓存空值(设短过期时间);② 布隆过滤器 前置拦截;③ 参数校验,拦截非法请求。

5.2 缓存击穿

Q:什么是缓存击穿?怎么解决? 热点 key 突然过期,大量并发请求瞬间压到 DB。
解决:① 互斥锁(只有一个线程去加载,其他等待);② 逻辑过期(key 不过期,value 存逻辑过期时间,异步更新);③ 热点数据永不过期 + 后台刷新。

5.3 缓存雪崩

Q:什么是缓存雪崩?怎么解决? 大量 key 同时过期 或 Redis 集群宕机,请求全部涌向 DB,DB 被打挂。
解决:① 过期时间加随机值打散;② 多级缓存(本地 + Redis);③ 熔断限流降级;④ Redis 高可用(主从+哨兵/集群)。

5.4 缓存一致性

Q:如何保证缓存与数据库的一致性? 推荐:先更新 DB,再删除缓存(Cache Aside)。
延迟双删:更新 DB 前删缓存 → 更新 DB → 延迟再删一次。
更强的方案:Canal 订阅 binlog 异步删缓存,或分布式锁串行化。
注意:删除比更新好(避免并发写导致脏数据);写成"更新 DB + 更新缓存"在并发下更易不一致。
为什么"先删缓存再更新 DB"不行:并发下 A 删缓存 → B 读旧值回填缓存 → A 更新 DB,缓存里留了旧数据。

6. 分布式锁

6.1 正确实现

错误写法:SETNX + EXPIRE 两条命令不是原子的(中间宕机则死锁);直接 DEL 会误删别人的锁。

6.2 Redlock 争议

7. 内存与淘汰

7.1 过期删除策略

7.2 内存淘汰策略(maxmemory-policy)

策略说明
noeviction默认,不淘汰,写入报错
allkeys-lru所有 key 中淘汰最近最少用(缓存常用)
allkeys-lfu所有 key 中淘汰使用频率最低(4.0+)
allkeys-random随机淘汰
volatile-lru / lfu / random / ttl只在设置了过期时间的 key 中淘汰

LRU 用近似算法(采样 N 个 key,选最久未用的),避免维护完整链表。

8. 速查清单

主题一句话答案
为什么快内存 + 单线程无锁 + IO 多路复用 + 高效结构
Redis 6 多线程只有网络 IO 多线程,命令执行仍单线程
RDB vs AOF快照(快、可能丢) vs 命令日志(安全、慢)
混合持久化RDB 头 + AOF 尾,4.0+
ZSet 底层元素少用 listpack,多则 skiplist + dict
Cluster 槽位16384 个,CRC16 % 16384
穿透查不存在数据 → 缓存空值 / 布隆过滤器
击穿热点 key 过期 → 互斥锁 / 逻辑过期
雪崩大量 key 同时过期 → 随机过期时间 + 高可用
一致性先更新 DB 再删缓存 + 延迟双删
分布式锁SET NX EX + Lua 释放 + 看门狗续期
淘汰策略默认 noeviction;缓存推荐 allkeys-lru