题目来源说明:本文题目整理自 BOSS 直聘、拉勾、猎聘等招聘平台上后端/Java 开发岗位 JD 中的 Redis 技能要求(如"高并发缓存架构""缓存一致性""分布式锁"等关键词),并结合一线互联网公司公开面经汇总而成,每题附工程化参考答案。
上一期我们盘了 Java 并发(线程池 / AQS / 虚拟线程),本期按招聘 JD 高频词继续轮换,主题是 Redis。以下 12 题在近 30 天的 JD 与面经中出镜率最高,建议按"概念 → 工程 → 争议"三层来回答。
一、缓存三大经典问题:穿透、击穿、雪崩
这是 JD 中"高并发缓存架构"关键词背后几乎必问的一组题,面试官通常要求一次性说清三者区别 + 每种的 2 种以上解法。
缓存穿透:查询根本不存在的数据(如恶意用 id=-1 扫接口),缓存永远未命中,请求全打到数据库。
- 解法 1:缓存空值(短 TTL,如 60s),防止同一 key 反复穿透;
- 解法 2:布隆过滤器前置拦截,把全量合法 key 放入布隆过滤器,判定不存在直接返回;
- 解法 3:入口层做参数合法性校验与限流(如 id 范围、格式校验)。
缓存击穿 :某个热点 key 在过期瞬间,大量并发同时打到数据库。
- 解法 1:互斥锁重建------只允许一个线程去查库回填,其他线程等待或返回旧值;
- 解法 2:热点 key 逻辑过期------物理上不设置 TTL,在 value 里存过期时间,发现逻辑过期后异步重建;
- 解法 3:对极热 key 直接延长 TTL 或设置永不过期 + 后台定时刷新。
缓存雪崩 :大量 key 同时过期,或 Redis 实例宕机,请求洪峰整体压到数据库。
- 解法 1:TTL 加随机抖动(如基础 30min ± 5min),打散过期时间;
- 解法 2:集群高可用(哨兵 / Cluster),避免单点整体失效;
- 解法 3:多级缓存(本地缓存 Caffeine + Redis),并在 DB 层加限流熔断兜底。
加分答法:主动提"先更新数据库,再删缓存(Cache Aside)"是当前主流一致性方案,并说明为什么一般不用"更新缓存"(并发写覆盖 + 冷数据浪费)。
二、缓存与数据库一致性:延迟双删、Canal 订阅 binlog
JD 里"缓存一致性"是近一年新增频率最高的关键词之一。
- Cache Aside 的不一致窗口 :删缓存失败怎么办?
- 方案 A:消息队列重试删除,保证最终一致;
- 方案 B:订阅 MySQL binlog(Canal),由 binlog 变更驱动删除缓存,业务代码解耦。
- 延迟双删:写场景下"删缓存 → 更新库 → 延迟几百毫秒再删一次",用于覆盖"读写并发时旧值回填"的窗口。要能说出延迟时间如何估算(读接口 P99 耗时 + 业务冗余)。
- 强一致要不要上? 一般答:缓存场景接受最终一致,秒级窗口可容忍;真要强一致就不该用缓存。能主动做这个权衡,面试官印象分很高。
三、大 key 与热 key 治理
JD 中"Redis 性能调优"的高频落点,大厂面经里常要求给出量化标准 和具体治理手段。
大 key 的常见阈值(业界通行口径):String 超过 10KB、集合类型元素超过 5000 个或总大小超过 10MB 即需关注。
危害:慢查询阻塞(Redis 单线程处理大 value 串行耗时)、主从同步卡顿、删除时阻塞(DEL 大集合是同步的)、内存倾斜。
治理手段:
- 拆分:大 hash 按 field 哈希拆成多个小 key;
- 压缩:value 序列化后压缩存储;
- 异步删除:Redis 4.0+ 用
UNLINK替代DEL,或开启lazyfree-lazy-expire; - 排查工具:
redis-cli --bigkeys、MEMORY USAGE、RDB 离线分析工具。
热 key:单 key QPS 极高,打满单个分片。治理:本地缓存兜底(Caffeine)、key 加随机后缀分散到多个节点、读写分离扩容从库。
四、持久化:RDB 与 AOF 怎么选
JD 写"缓存/存储"的岗位常问持久化细节:
- RDB:定时快照,fork 子进程写盘。优点是文件小、恢复快;缺点是两次快照之间的数据会丢。fork 瞬间的 copy-on-write 可能造成内存翻倍,大实例要关注。
- AOF :追加写命令日志。
everysec是主流配置(最多丢 1 秒),AOF 重写(bgrewriteaof)压缩文件体积。 - 混合持久化 (4.0+,
aof-use-rdb-preamble):重写时 RDB 打底 + 增量 AOF,恢复快且丢得少,当前生产默认推荐。能说出这个基本就是加分项。
五、过期删除与内存淘汰策略
高频送分题,但要答得成体系:
- 过期删除:惰性删除(访问时检查)+ 定期抽样删除(每 100ms 抽样清理),两者结合。
- 内存淘汰 (
maxmemory-policy)8 种策略,重点记:noeviction(默认,写报错);allkeys-lru:纯缓存场景最常用;volatile-lru / volatile-ttl:只淘汰设了 TTL 的 key;allkeys-lfu(4.0+):有明显冷热分离的场景更优。
- 能说清 LRU 与 LFU 的实现差异(Redis 的 LRU 是近似 LRU,采样淘汰;LFU 用 Morris 计数器做对数衰减)即可超越多数候选人。
六、Redis 6/7 新特性:IO 多线程与 listpack
近一年 JD 中"Redis 7"开始出现,面经常问"你了解新版本特性吗":
- 网络 IO 多线程 (6.0+):命令执行仍是单线程,但网络读写与协议解析多线程化,
io-threads建议设 2~4。要澄清一个误区:Redis 的线程安全模型没变,数据操作依然是单线程无锁的。 - listpack 替代 ziplist(7.0):小哈希/小 zset 的底层编码从 ziplist 换成 listpack,解决了 ziplist 级联更新(cascade update)的问题。被问"跳表底层"时也可以顺势提:zset 在元素少时用 listpack,多了才转 skiplist + dict。
- Function 与 Multi-part AOF:7.0 把 Lua 脚本升级为可持久化的 Function,AOF 拆为 base + incr 多文件,重写不再整文件替换。
七、分布式锁:从 SETNX 到 Redlock 的争议
JD 里"分布式锁"几乎必问,标准答题路径:
- 为什么要用
SET key value NX EX ttl一条命令:保证"加锁 + 设置过期时间"原子性,避免 SETNX 后进程挂掉造成死锁; - 锁误删问题 :value 存唯一标识(UUID + 线程 ID),释放锁用 Lua 脚本做"判断是自己的锁再删"的原子操作;
- 看门狗续期:业务没执行完锁先过期怎么办?Redisson 的 watchdog 默认 30s 锁,每 10s 续期;
- 主从切换锁丢失 :主节点加锁后未同步到从库就宕机,新主上锁不存在------由此引出 RedLock(向 N 个独立节点加锁,过半成功才算持有)。
- 争议加分点 :Martin Kleppmann 与 antirez 的著名论战------RedLock 的时钟假设被质疑,强正确性场景业界更推荐 ZooKeeper/etcd 或 fencing token 方案;大多数业务锁用"单实例 Redis + Redisson"已足够。面试中能主动说出这个 trade-off,基本是这一题的满分答法。
八、主从、哨兵与 Cluster 的取舍
按规模分三层回答:
- 主从复制:读写分离,但主挂了需手动切换;全量同步走 RDB,增量靠 repl_backlog 环形缓冲区(缓冲区太小会导致频繁全量同步,这是隐藏追问点)。
- 哨兵(Sentinel) :自动故障转移 + 通知,适合数据量单机可容纳的场景;注意脑裂问题------
min-replicas-to-write可降低脑裂期间的数据丢失。 - Cluster :16384 个 hash slot 分片,解决单机内存与写瓶颈;代价是多 key 操作受 slot 限制(hash tag
{}解决)、运维复杂度上升。面试常追问"为什么是 16384"------心跳包中 slot 位图用 2KB 即可承载,兼顾 gossip 效率与信息量。
九、其他高频小题速答
- 为什么快:纯内存、单线程无锁竞争、IO 多路复用(epoll)、高效数据结构(SDS、跳表、listpack)。
- 跳表为什么不用红黑树:实现简单、范围查询天然友好(底层有序链表)、按概率维护平衡无需旋转。
- Pipeline vs 事务 vs Lua:Pipeline 只省 RTT 不保证原子;MULTI/EXEC 入队但不支持回滚;Lua 脚本整体原子执行,复杂原子操作首选。
- Redis 为什么设计成单线程:瓶颈在内存与网络而非 CPU,单线程避免锁与上下文切换,可维护性好。
写在最后
Redis 面试的答题主线是:先答概念,再答工程参数,最后主动暴露 trade-off (比如 RedLock 争议、混合持久化、逻辑过期 vs 互斥锁)。本系列下期预告:MySQL 索引与 MVCC,同样是 JD 关键词榜前三的主题,欢迎关注追更。