外观
Redis 速查卡
🎯 覆盖 32 题 | ⭐ 10 题 | 预计扫描 12 分钟 📌 先看⭐一句话答案 → 展开要点 → 自测清单检验
一、数据结构
知识地图:String(SDS) / Hash(listpack→hashtable) / List(quicklist) / Set(hashtable) / ZSet(skiplist+hashtable)
⭐ 五种数据类型 + 场景
| 类型 | 底层 | 典型场景 |
|---|---|---|
| String | SDS | 缓存对象、计数器(INCR)、分布式锁(SETNX)、Session |
| Hash | listpack/hashtable | 对象属性(用户信息)、购物车 |
| List | listpack/quicklist | 消息队列(LPUSH+BRPOP)、Feed流 |
| Set | listpack/hashtable | 去重(抽奖)、共同好友(SINTER) |
| ZSet | listpack/skiplist+hashtable | 排行榜(ZREVRANGE)、延迟队列(score=时间戳) |
⭐ ZSet 底层 = 跳表 + 哈希表
一句话: ZSet 同时维护 skiplist(按 score 排序,支持范围查询 O(log N))和 hashtable(member→score 映射,O(1) 查分数),两者结合实现"既能排序又能快查"。
跳表核心: 多层链表 + 随机层高(P=0.25),查询 O(log N);比红黑树实现更简单、范围查询更自然、并发友好
为什么不用 B+ 树: Redis 数据在内存,B+ 树的磁盘页对齐优势无意义;跳表实现更简单
二、性能原理
⭐ Redis 为什么快
一句话: 六大原因——纯内存操作 + 命令执行单线程(无锁无切换) + IO 多路复用(epoll) + 高效数据结构 + 渐进式 rehash + RESP 协议简单。
⚠️ 易错:"单线程"指命令执行单线程;Redis 6.0+ 网络 IO 读写已经是多线程
↳ 追问:单线程但仍需分布式锁(SETNX/Lua),因为多个命令之间不是原子的(如先 GET 再 SET)
三、缓存问题
⭐ 穿透 / 击穿 / 雪崩
一句话: 穿透=查不存在的 key(恶意攻击),击穿=热点 key 突然过期,雪崩=大量 key 同时过期或 Redis 宕机。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 穿透 | 查询不存在的数据,缓存和 DB 都没有 | ① 缓存空值(短TTL) ② 布隆过滤器(推荐) |
| 击穿 | 热点 key 过期,大量请求打到 DB | ① 互斥锁重建 ② 逻辑过期(异步刷新,返回旧数据) |
| 雪崩 | 大量 key 同时过期 / Redis 宕机 | ① TTL 加随机偏移 ② 集群高可用 ③ 多级缓存 |
布隆过滤器: m 位 bit 数组 + k 个哈希函数;"不存在"100%确定,"存在"有误判率
⚠️ 易错:三者不要混淆——穿透是数据根本不存在,击穿是热点 key 过期,雪崩是大面积过期/宕机
四、持久化与过期
⭐ 过期策略 + 淘汰策略
一句话: 过期用惰性删除(访问时检查)+定期删除(100ms抽样清理)双策略结合;内存满时触发淘汰策略,推荐 allkeys-lru。
过期: 惰性删除(按需,CPU友好但可能积压) + 定期删除(抽样,兜底清理)
淘汰策略TOP3:
| 策略 | 范围 | 适用 |
|---|---|---|
| allkeys-lru | 所有 key | 缓存场景(推荐) |
| allkeys-lfu | 所有 key | 访问频率差异大 |
| volatile-lru | 有过期时间的 key | 混合场景(保护永久key) |
RDB vs AOF: RDB=定时快照(文件小恢复快但可能丢分钟级数据);AOF=记录每条写命令(默认每秒fsync最多丢1s);推荐混合持久化(RDB+AOF增量)
五、分布式锁
⭐ Redis 分布式锁演进
一句话: 从 SETNX(不原子) → SET EX NX(原子加锁) → Lua 脚本释放(防误删) → Redisson(自动续期 Watch Dog + 可重入),生产用 Redisson。
四阶段演进:
① SETNX + DEL → 问题:SETNX和EXPIRE不原子,宕机死锁
② SET key uuid EX 30 NX → 问题:业务超时锁自动过期,可能误删他人锁
③ Lua 脚本释放(检查uuid再DEL) → 问题:业务超时仍可能导致锁过期
④ Redisson(Watch Dog每10s自动续期) → 生产推荐,支持可重入/公平锁/读写锁Redisson 加锁 Lua 核心: 锁用 Hash 结构(key=锁名, field=threadId, value=重入次数),不存在则 hset + pexpire;同线程则 hincrby 重入
Watch Dog: 默认锁 30s,每 10s 检查续期;线程正常释放则取消续期;JVM 宕机则 30s 后自动过期
⚠️ 易错:主从切换可能导致锁丢失(异步复制);Redlock 方案争议大,大多数场景单机 Redisson 够用;强一致用 ZooKeeper
六、一致性与集群
⭐ Redis 和 MySQL 数据一致性
一句话: 写操作先更新 DB 再删除 Cache(不是更新 Cache),用删除保证最终一致;强可靠用 Canal 监听 binlog + MQ 异步删除。
为什么是"删除"不是"更新": 更新需保证 DB+Cache 两步原子,复杂;删除后下次读自动重建,简单正确
为什么先更新 DB 再删 Cache(不反过来):
错误(先删Cache再更新DB):A删Cache → B读Cache miss读到旧DB写回Cache → A更新DB → 不一致!
正确(先更新DB再删Cache):微小窗口后Cache被删,下次读到新数据 → 最终一致加强方案: 延迟双删(先删→更新DB→延迟500ms→再删)/ Canal 监听 binlog 异步删除
六B、集群架构(Sentinel / Cluster)
⭐ Sentinel 哨兵模式
一句话: 三大功能——监控主从节点健康 + 自动故障转移(选新主) + 通知客户端新主地址;至少 3 个 Sentinel 奇数部署。
下线判断: SDOWN(主观,单个哨兵超时) → ODOWN(客观,quorum 个哨兵共识) → 触发故障转移
选新主顺序: 过滤不健康 → slave-priority 小的优先 → 复制偏移量大的优先(数据最新) → runId 最小
⚠️ 局限:写仍集中在单主,无法水平扩展写能力和存储容量
⭐ Redis Cluster 架构
一句话: 去中心化 P2P 架构,16384 个 hash slot 分片到多主节点,CRC16(key) % 16384 决定路由;每主配从实现高可用。
核心要点:
slot 数量 = 16384(心跳 bitmap 仅 2KB,工程平衡点)
路由:CRC16(key) % 16384 → slot → node
Hash Tag:{user:1001}:info 强制相关 key 落同一 slot
Gossip 通信:MEET/PING/PONG/FAIL,节点间交换状态,最终一致故障转移: PFAIL(单节点超时) → 半数主节点确认 → FAIL → 从节点选举(偏移量最大优先) → 接管 slot
扩缩容: 添加节点 → resharding(slot 迁移,IMPORTING+MIGRATING+MIGRATE 逐 key 搬) → 迁移中 ASK 临时重定向
MOVED vs ASK: MOVED = slot 永久迁完,更新客户端映射表;ASK = slot 正在迁移,仅本次临时重定向
⭐ 三种部署模式对比
| 模式 | 高可用 | 数据分片 | 写扩展 | 适用 |
|---|---|---|---|---|
| 单机 | 无 | 不支持 | 不支持 | 开发测试 |
| 哨兵 | 自动切换 | 不支持(全量在主) | 不支持(单主) | 读多写少,数据量可单机承载 |
| Cluster | 自动切换 | 16384 slot | 多主并行写 | 大数据量、高并发、水平扩展 |
选型: 数据量单机装得下 → 哨兵(1主2从3哨兵);装不下或写QPS高 → Cluster(≥3主3从)
补充速览
| 关键词 | 核心答案 |
|---|---|
| SDS | O(1)获取长度 + 空间预分配 + 惰性释放 + 二进制安全 |
| 渐进式 rehash | 扩容时不一次性迁移,每次操作顺带迁移一个桶;查询先查 ht[0] 再查 ht[1] |
| Bitmap | 底层是 String(SDS),按 bit 位操作;最大 512MB = 42.9 亿 bit;用于签到/UV统计 |
| 热 key | 本地缓存(Caffeine) + 分散 key(热key复制多份随机读) |
| 大 key | 拆分 + UNLINK 异步删除 + HSCAN 分批读 |
| 逻辑过期 | key 永不过期,value 存过期时间;过期后互斥锁异步重建 + 返回旧数据 |
| 主从复制 | 全量(RDB+buffer) → 增量(repl_backlog) → 命令传播(异步) |
| 本地缓存一致性 | 短 TTL(5s) + Redis Pub/Sub 广播失效;多实例各自有 Caffeine 需通知清除 |
| 限流 | 固定窗口(INCR,有边界问题) → 滑动窗口(ZSet,精确) → 令牌桶(允许突发) |
| Redlock | 5个独立实例,过半加锁成功;争议大,强一致用 ZK/etcd |
| Redis vs ZK 锁 | Redis: AP/高性能/TTL释放;ZK: CP/强一致/临时节点自动释放 |
| Sentinel 故障转移 | SDOWN→ODOWN→Raft选Leader→选新主(偏移量最大)→SLAVEOF NO ONE |
| 16384 slot | CRC16(key)%16384;bitmap仅2KB;hash tag {}强制同slot |
| Gossip 协议 | MEET/PING/PONG/FAIL四种消息,节点间随机交换,最终一致 |
| Cluster 故障转移 | PFAIL→半数主节点确认FAIL→从节点选举→接管slot→广播PONG |
| Slot 迁移 | IMPORTING+MIGRATING+MIGRATE逐key搬;迁移中ASK临时重定向 |
🧠 助记汇总
| 口诀 | 含义 |
|---|---|
| 穿不存,击过期,崩大面积 | 穿透=数据不存在;击穿=热key过期;雪崩=大面积过期/宕机 |
| 先DB后删Cache | 缓存一致性写操作顺序 |
| 内单路结渐简 | Redis快的六原因:内存/单线程/多路复用/数据结构/渐进rehash/协议简单 |
| 跳哈双结构 | ZSet = 跳表(有序) + 哈希表(O(1)查分数) |
| NX→EX NX→Lua→Redisson | 分布式锁四阶段演进 |
| 主客观,选最新 | Sentinel:SDOWN→ODOWN→选偏移量最大的从节点 |
| 一万六千三百八十四 | 16384 个 hash slot,CRC16 取模路由 |
| MOVED 永久,ASK 临时 | 客户端路由重定向的两种类型 |
✅ 自测清单
| # | 问题 | 你能说出... |
|---|---|---|
| 1 | 五种数据类型 | 底层结构 + 各自典型场景 |
| 2 | ZSet 底层 | skiplist+hashtable 双结构 + 为什么不用B+树 |
| 3 | Redis 为什么快 | 六大原因 + "单线程"具体指什么 |
| 4 | 穿透/击穿/雪崩 | 三者区别 + 各自解决方案 |
| 5 | 分布式锁 | 四阶段演进 + Redisson Watch Dog 原理 |
| 6 | 缓存一致性 | 先更新DB再删Cache + 为什么是删除不是更新 |
| 7 | 过期+淘汰 | 双删除策略 + 推荐淘汰策略 |
| 8 | RDB vs AOF | 各自优缺点 + 推荐混合 |
| 9 | Sentinel 哨兵 | SDOWN/ODOWN区别 + 选新主规则 + 局限性 |
| 10 | Cluster 架构 | 16384 slot + CRC16路由 + hash tag |
| 11 | Cluster 故障转移 | PFAIL→FAIL + 从节点选举 + slot接管 |
| 12 | MOVED vs ASK | 永久重定向 vs 临时重定向 + 客户端行为区别 |
| 13 | 三种部署模式 | 单机/哨兵/Cluster 适用场景 + 选型决策 |
💡 首次全部过一遍 → 第2天只过答不上来的 → 第4天再复习 → 面试前一天最后扫一遍