Java 后端真实面试专题 · Redis 篇
Redis 和 MySQL 并列高频。每题三段: ① 标准答(讲透:是什么+为什么+怎么做+原理)→ ② 拓展(成体系带出关联点和必追问的,答一题等于答一片)→ ③ 怎么接到你自己的项目。
年限标签:
🟢 3年内🔴 3年+
1. 🟢 Redis 为什么这么快?单线程为什么不慢?
标准答:四个原因——
- 纯内存操作:数据在内存,读写就是纳秒级。
- 单线程模型:命令执行是单线程,避免了多线程的锁竞争和上下文切换开销。
- IO 多路复用:基于 epoll 的事件驱动,一个线程能高效处理大量连接。
- 高效数据结构:如 SDS、跳表、压缩列表、渐进式 rehash。
拓展:
- "单线程为什么不是瓶颈?"——Redis 的瓶颈在内存和网络 IO而不是 CPU,单线程省去锁开销反而更稳更简单。
- "Redis 6.0 不是多线程了吗?"——6.0 引入多线程只用于网络 IO 的读写和协议解析,命令执行仍是单线程,所以不用担心并发安全。
- "什么操作会阻塞单线程?"——
keys *、大 key 的del、复杂的sort,这些要避免(用scan替代 keys)。
往项目引 ⭐:"我项目把首页商品、热门排行这些读多写少的数据放 Redis,QPS 扛住了大促流量、DB 压力降了一大半。也正因为单线程,我们绝不在线上用 keys *,统计 key 都用 scan 渐进遍历。"
2. 🟢 Redis 有哪些数据类型?底层结构和适用场景?
标准答:五种基本类型——
- String:缓存、计数(
incr)、分布式锁。底层 SDS。 - Hash:存对象(如
user:1的各字段,可单独改一个字段)。底层 ziplist/hashtable。 - List:消息队列、最新列表。底层 quicklist。
- Set:去重、共同好友(
sinter交集)。底层 intset/hashtable。 - ZSet:排行榜、延迟队列。底层 ziplist/skiplist。
拓展:
- 进阶三个特殊类型——bitmap(签到、布隆过滤器)、HyperLogLog(UV 统计,省内存有误差)、GEO(附近的人)。能说出这三个是加分项。
- "ZSet 为什么用跳表不用红黑树?"——跳表实现简单、范围查询方便、并发友好。
- "Hash 什么时候从 ziplist 转 hashtable?"——元素超 128 个或值超 64 字节。
往项目引 ⭐:"我项目里每种类型都对着一个真实功能:ZSet 做实时热销榜(zincrby 累加、zrevrange 取 Top10)、Hash 存购物车、Set 做活动去重、bitmap 做签到——不是背概念,是真用过。"
3. 🟢 缓存穿透、击穿、雪崩分别是什么?怎么解决?(必问)
标准答:
- 穿透:查一个根本不存在的数据,缓存和 DB 都没有,每次都打到 DB(常被恶意攻击)。解决:① 缓存空值(设短过期);② 布隆过滤器拦截非法 key。
- 击穿:某个热点 key 过期的瞬间,大量请求同时打到 DB。解决:① 互斥锁,只让一个线程重建缓存;② 热点 key 逻辑过期/不过期。
- 雪崩:大量 key 同一时间过期,或 Redis 宕机,请求全压到 DB。解决:① 过期时间加随机值打散;② 多级缓存;③ Redis 高可用 + 限流降级兜底。
拓展:
- 三者区别一句话:穿透是"查不存在的",击穿是"单个热点失效",雪崩是"大面积同时失效"。
- "布隆过滤器原理?"——多个哈希函数映射到位数组,能判断"一定不存在"或"可能存在",有误判、但省空间、不能删除(用计数布隆可删)。
- 互斥锁重建要注意锁本身的超时,别把请求都堵死。
往项目引 ⭐:"我项目商品详情接口用了三招:布隆过滤器挡非法商品 id 防穿透、热点商品用逻辑过期防击穿、所有 key 过期时间加随机值防雪崩,大促期间缓存层零事故。"
4. 🟢 RDB 和 AOF 持久化的区别?怎么选?
标准答:
- RDB:定时把内存快照 dump 成一个二进制文件。优点文件小、恢复快;缺点是两次快照之间宕机会丢数据。
- AOF:记录每条写命令,追加到文件。优点更安全(可配每秒刷盘,最多丢 1 秒);缺点文件大、恢复慢。
- 选择:生产常用 RDB + AOF 混合持久化——AOF 保证少丢数据,重启时用 RDB 部分加速恢复。
拓展:
- "AOF 文件越来越大怎么办?"——AOF 重写(rewrite),用最简命令重建当前数据状态,压缩体积。
- "AOF 刷盘策略?"——always(每条都刷,最安全最慢)、everysec(每秒,默认,平衡)、no(交给操作系统)。
- "主从 + 持久化的关系?"——从库也可以开持久化做多份备份。
往项目引 ⭐:"我项目 Redis 主要做缓存、数据能从 DB 重建,所以对持久化要求不高、用 RDB 够了;但做分布式锁的那个实例开了 AOF,因为锁信息丢了影响业务。按数据重要性分别配置。"
5. 🟢 Redis 的 key 过期了是怎么删除的?内存满了怎么淘汰?
标准答:
- 过期删除:惰性删除(访问到 key 时才检查、过期就删)+ 定期删除(每隔一段时间随机抽一批检查删除)。两者结合,平衡 CPU 和内存。
- 内存淘汰:内存达到
maxmemory时按策略淘汰,8 种——对所有 key 的(allkeys-lru/lfu/random)、只对设了过期时间的(volatile-lru/lfu/ttl/random)、noeviction(不淘汰、写报错)。常用allkeys-lru。
拓展:
- "为什么不立即删除过期 key?"——给每个 key 开定时器太耗 CPU,所以用惰性+定期的折中。
- "惰性删除有什么问题?"——过期了但一直没被访问的 key 会占内存,靠内存淘汰兜底。
- "LRU 和 LFU 区别?"——LRU 看"最近有没有用",LFU 看"用得频不频繁",LFU 更适合有明显冷热的数据。
往项目引 ⭐:"我项目纯缓存场景设 allkeys-lru,让冷数据被自动挤掉、热数据常驻,命中率稳定在 95% 以上。还排查过内存涨不下去——就是很多过期 key 没被访问没及时删,调整策略后好了。"
6. 🟢 怎么保证 Redis 缓存和 MySQL 的数据一致性?
标准答:最常用 Cache Aside(旁路缓存):
- 读:先查缓存,没有就查 DB 并回填缓存。
- 写:先更新 DB,再删除缓存(而不是更新缓存)。 删除而非更新是为了避免并发写时旧值覆盖新值;先更库后删缓存能减少不一致窗口。
拓展:
- "删缓存失败怎么办?"——延迟双删(更新前删一次、更新后延迟几百毫秒再删一次),或用 MQ/canal 监听 binlog 异步删缓存做兜底重试。
- "为什么不先删缓存再更库?"——并发下可能"删缓存→别的请求把旧值读回缓存→才更库",反而不一致。
- 强一致很难、代价高,一般做最终一致就够。
往项目引 ⭐:"我项目商品改价用'更新 DB + 删缓存',再加 canal 监听 binlog 兜底删,保证用户最终看到新价格、不会长期读到旧价。要求高的地方加延迟双删。"
7. 🔴 Redis 分布式锁怎么实现?有哪些坑?
标准答:加锁用 SET key 唯一值 NX EX 30——NX 保证只有一个客户端能设置成功,EX 设过期防止持锁者宕机导致死锁,value 用唯一标识(如 UUID + 线程 id)。解锁用 Lua 脚本保证"判断是不是自己的锁 + 删除"两步原子。
拓展:
- 坑一:业务没执行完锁就过期了——别的线程拿到锁,出现并发。解决:用 Redisson 的看门狗自动续期。
- 坑二:解锁误删别人的锁——所以 value 要唯一、解锁要先判断。
- 坑三:主从切换锁丢失——主库加锁成功还没同步就宕机,从库上锁没了。极端一致用 RedLock(向多数节点加锁)。
- 直接
setnx+del不带唯一标识、不设过期是错误写法。
往项目引 ⭐:"我项目定时任务多实例部署,用 Redisson 分布式锁保证同一任务同一时刻只有一个实例跑,看门狗自动续期解决了'长任务锁提前过期'的问题,不用自己手写续期逻辑。"
8. 🟢 什么是热 key 和大 key?有什么危害,怎么解决?
标准答:
- 热 key:访问量极高的 key(如爆款商品、热搜),可能把单个 Redis 节点 CPU/网卡打满。解决:本地缓存(Caffeine)+ key 拆分到多个节点 + 多副本读。
- 大 key:value 很大的 key(如几万元素的 List、几 MB 的 String),操作慢、阻塞单线程、内存分布不均、删除时卡顿。解决:拆分(大 Hash 拆成多个小 Hash)、用
scan/unlink渐进删除而不是del。
拓展:
- "怎么发现大 key?"——
redis-cli --bigkeys、memory usage key、扫描分析。 - "为什么大 key 删除会阻塞?"——单线程删大对象耗时,Redis 4.0+ 用
unlink异步删除。 - 热 key 在集群下还会导致数据倾斜(某个分片特别忙)。
往项目引 ⭐:"我项目大促前会用 --bigkeys 扫一遍大 key 提前拆分,给爆款商品做 Caffeine 本地缓存 + Redis 二级缓存,避免热 key 把某个分片打挂——这是大促保障的常规动作。"
9. 🔴 Redis 的主从、哨兵、Cluster 分别解决什么问题?
标准答:
- 主从复制:主写从读,实现读扩展和数据备份。但主挂了不能自动恢复。
- 哨兵(Sentinel):监控主从健康,主库宕机时自动故障转移(选一个从库升为主)、通知客户端新主地址。解决高可用,但不解决容量。
- Cluster(集群):数据按 16384 个哈希槽分布到多个主节点(每个主带从),既分片扩容又高可用。解决单机容量和写性能瓶颈。
拓展:
- "Cluster 怎么定位 key 在哪个节点?"——
CRC16(key) % 16384算出槽、槽分配给某个节点。 - "Cluster 支持多 key 操作吗?"——跨槽的多 key 操作受限,可用 hash tag(
{})把相关 key 放同一槽。 - 演进路线:单机 → 主从+哨兵(高可用)→ Cluster(容量也扛不住时)。
往项目引 ⭐:"我项目早期单机 + RDB 备份,量大后上哨兵保证高可用(演练过杀主节点,几秒切主),再后来数据量和写入都涨上来才迁到 Cluster 分片。是按瓶颈一步步演进的,不是一上来就上集群。"
10. 🔴 用 Redis 怎么实现接口限流?
标准答:几种方式——
- 固定窗口:
incr计数 + 第一次设过期,超阈值就拒绝。简单但有临界突刺。 - 滑动窗口:用 ZSet 存请求时间戳,按时间范围
zcount统计当前窗口请求数,更平滑。 - 令牌桶/漏桶:用 Lua 脚本保证原子,允许一定突发。
拓展:
- "固定窗口的临界问题?"——窗口边界前后各来一批,瞬时可能是阈值的两倍。
- 生产常用 Redis + Lua 实现令牌桶,或直接用 Sentinel/网关限流。
- Lua 保证"取令牌 + 判断 + 扣减"原子,避免并发超发。
往项目引 ⭐:"我项目短信验证码接口防刷,用 Redis 对手机号做滑动窗口限流——一分钟最多发一条,超了直接拦,用 Lua 保证并发下计数准确。"
11. 🟢 Redis 的 pipeline 是什么?和事务、Lua 的区别?
标准答:
- pipeline(管道):把多条命令一次性打包发送、一次拿回所有结果,减少网络往返(RTT)。只是批量发送,不保证原子、命令间不能依赖。
- 事务(MULTI/EXEC):命令入队后一起执行,但不支持回滚、中间不能拿结果做判断。
- Lua 脚本:在服务端原子执行,可以带逻辑判断(读-判断-写),是实现复杂原子操作的首选。
拓展:
- "pipeline 和事务能一起用吗?"——可以,pipeline 里包 MULTI/EXEC。
- 库存预扣、分布式锁解锁这种"读+判断+写"必须用 Lua。
- 批量操作优先 pipeline,别 for 循环单条发(每条一次 RTT 很慢)。
往项目引 ⭐:"我项目批量查 100 个商品库存,原来循环 100 次网络往返很慢,改成 pipeline 一次发回快了好几倍;秒杀预扣库存则用 Lua 脚本保证'判库存→扣库存'原子,杜绝超卖。"
12. 🔴 ZSet 的底层是什么?为什么排行榜用它?
标准答:ZSet 底层是 跳表(skiplist)+ 哈希表 的组合(元素少时用 ziplist 省内存):
- 跳表保证按 score 有序,支持范围查询和按排名取(
zrange)。 - 哈希表保证按成员 O(1) 查到它的 score(
zscore)。 两者配合,既能快速排名又能快速查分。
拓展:
- "跳表是什么?"——多层链表,上层是下层的"索引",查找类似二分、平均 O(log n)。
- "为什么不用红黑树?"——跳表实现简单、范围查询直接顺着底层链表走、并发更好控制。
- ziplist 转 skiplist 的阈值:元素超 128 或成员超 64 字节。
往项目引 ⭐:"我项目实时热销榜用 ZSet——下单时 zincrby 累加销量、展示时 zrevrange 0 9 取 Top10、查某商品排名用 zrevrank。正是看中跳表能高效排名又能查分。"
13. 🟢 缓存预热和缓存降级是什么?
标准答:
- 缓存预热:系统上线或大促前,提前把热点数据加载进缓存,避免冷启动时请求全打到 DB。可用启动任务或定时任务加载。
- 缓存降级:当缓存或下游不可用时,返回兜底数据/默认值/旧数据,保证核心流程可用,常配合熔断(Sentinel)。
拓展:
- 预热要选真正的热点(可根据历史访问统计)。
- 降级是有损服务——牺牲部分体验保整体可用。
- 还有"缓存击穿"场景下,预热 + 逻辑过期能避免热点重建风暴。
往项目引 ⭐:"我项目大促前用定时任务把 Top 商品预热进 Redis;Redis 万一挂了,详情接口降级返回本地缓存的基础信息,不让整个页面崩——预热保性能、降级保可用。"
14. 🔴 Redis 怎么实现延迟队列?和 MQ 比怎么选?
标准答:用 ZSet,score 存任务的到期时间戳,消费端轮询 zrangebyscore 取出到期的任务处理(取出后删除)。或直接用 Redisson 的 RDelayedQueue。
拓展:
- "和 RocketMQ 延迟消息比?"——Redis 方案轻量但要自己保证可靠性(消费失败重试、宕机恢复);MQ 方案更可靠、量大首选。
- 轮询要注意频率和并发取出的原子性(用 Lua 取+删)。
- 还可以用 Redis 的 keyspace 过期通知,但不保证及时和可靠。
往项目引 ⭐:"我项目订单 30 分钟未支付自动取消,早期用 Redis ZSet 做延迟队列(score 存取消时间),后来订单量大、对可靠性要求高,就迁到了 RocketMQ 延迟消息。"
15. 🟢 Redis 和 Memcached 的区别?为什么选 Redis?
标准答:
- Redis:支持丰富数据结构(String/Hash/List/Set/ZSet)、持久化、主从/哨兵/集群、事务/Lua、发布订阅。
- Memcached:只有简单 KV、纯内存不持久化、多线程。 现在新项目基本都选 Redis,因为功能强太多。
拓展:
- "Memcached 有什么优势?"——多线程在纯 KV、超大并发下吞吐略高,但功能太单一。
- Redis 的数据结构和持久化、高可用是决定性优势。
往项目引 ⭐:"我项目所有缓存都用 Redis,正是因为要用 ZSet 排行榜、分布式锁、发布订阅这些 Memcached 给不了的能力,一个 Redis 顶 Memcached 加一堆中间件。"
16. 🔴 Redis 宕机了缓存全没了怎么办?怎么保证高可用?
标准答:
- 高可用部署:哨兵或 Cluster,主挂自动切换,避免单点。
- 持久化恢复:RDB/AOF,重启能恢复数据。
- 重建保护:缓存失效后用限流 + 互斥锁防止瞬间把 DB 打垮(防雪崩)。
- 多级缓存:本地缓存(Caffeine)兜底,Redis 挂了本地还能扛一会。
拓展:
- "切换期间的请求怎么办?"——本地缓存 + 限流降级扛过切换窗口(哨兵切换通常几秒)。
- 要演练故障转移,别等真宕机才发现配置有问题。
往项目引 ⭐:"我项目做了本地缓存兜底 + Redis 哨兵,演练时把 Redis 主节点杀掉,哨兵几秒切主、本地缓存扛过切换窗口、限流防止 DB 被打垮,整体没出大问题——高可用是设计 + 演练出来的。"
17. 🔴 Redis 的发布订阅和 Stream 了解吗?
标准答:
- 发布订阅(pub/sub):消息广播,订阅者收实时消息,但不持久化,订阅者不在线就丢消息。适合实时通知。
- Stream(5.0 新增):真正的消息队列,支持持久化、消费组、ack 确认、消息回溯,比 pub/sub 可靠。
拓展:
- pub/sub 常用于"通知各节点清本地缓存"这种允许丢的场景。
- Stream 弥补了 List 做队列的不足(支持消费组、不会重复消费)。
- 但要做正经 MQ 还是用 RocketMQ/Kafka,Redis Stream 适合轻量场景。
往项目引 ⭐:"我项目用 pub/sub 做多级缓存的一致性——Redis 数据变了广播一条消息,各应用节点收到后清掉自己的本地缓存,保证本地缓存不会长期脏。"
18. 🟢 Redis 事务支持回滚吗?为什么?
标准答:不支持回滚。MULTI 把命令入队、EXEC 一次性执行,即使中间某条命令执行出错,其他命令照样执行、不会回滚。
拓展:
- "为什么不支持回滚?"——Redis 设计哲学是简单高效,命令出错通常是编程错误(用错类型),运行时错误回滚意义不大。
- 入队阶段的语法错误会导致整个事务不执行;执行阶段的错误只影响那条命令。
- 要原子的"读-判断-写"用 Lua 脚本,比事务更实用。
往项目引 ⭐:"我项目需要原子操作时基本不用 Redis 事务,而是用 Lua 脚本——比如库存预扣,脚本里判断 + 扣减一气呵成、原子且能带逻辑,比 MULTI/EXEC 好用。"
19. 🔴 缓存和数据库双写一致性,有哪些方案?各自取舍?
标准答:
- 先更库再删缓存(Cache Aside,推荐):简单,存在极小不一致窗口。
- 延迟双删:更新前删一次、更新后延迟再删一次,覆盖并发回填旧值的情况。
- 订阅 binlog(canal)异步删缓存:解耦、可靠、能重试,是较优方案。
- 先更新缓存再更库:基本不用,容易不一致。
拓展:
- 这些都是最终一致,强一致代价太高(要加分布式锁串行化,性能差)。
- 要不要追求强一致看业务——商品价格能接受秒级延迟,账户余额就不能。
往项目引 ⭐:"我项目主路径用'更库 + 删缓存',再用 canal 监听 binlog 异步补偿删缓存做兜底,保证最终一致。对一致性要求高的少数场景才加延迟双删,按业务分级处理。"
20. 🟢 项目里 Redis 都用在哪些场景?
标准答:常见用途——缓存(热点数据、减 DB 压力)、分布式锁、计数器(点赞/库存)、排行榜(ZSet)、会话共享(Session)、限流、消息/延迟队列、去重(Set/布隆)、签到(bitmap)、UV 统计(HyperLogLog)。
拓展:
- 选 Redis 还是别的——缓存和高并发读写用 Redis,正经消息队列用 MQ,复杂搜索用 ES。
- 不要把 Redis 当数据库用(虽然有持久化),它是缓存/高性能存储的定位。
往项目引 ⭐:"我项目里 Redis 承担了缓存、分布式锁、排行榜、限流、签到好几个角色——能用一句话把每个场景对应到具体功能,面试官就知道我是真的在项目里用,而不是背的清单。"
你能答到第几层?
- 三段都能答、还能往项目引:Redis 这块你稳了。
- 标准答 + 拓展能成体系答:知识够,差接到项目说出来。
- 标准答都磕巴:Redis 有主线(数据结构 → 持久化 → 过期淘汰 → 高可用 → 应用场景),跟着学一遍就通。
这是面试专题的「Redis 篇」,网站上还有并发、MySQL、Spring、微服务、项目场景等系统整理。 🌐 更多真实面试专题与资料:smallredtech.com 💬 想系统学 / 简历与辅导咨询,加微信:Ahongbb666(备注「面试题」)