后台隔三差五就有朋友拿着网上的Redis题库来问我:“这50道题答案靠谱吗?是不是背完就能过?”
我的回答一向是:背题保底,但保不了你拿高评价。Redis面试题看起来散,实际上是有主线的。面试官不会平白无故问你“SDS是什么”,他问的是你有没有在真实环境里排查过问题。这篇东西我就按自己面试和被面试的经验,把最常见、最容易踩坑的50道Redis缓存题拆成五组,每道题都附上解题思路和加分点,方便你自测,也能当临考前的速查表。
1. 先别急着背题:Redis面试到底在考什么
1.1 五个考察维度
我自己面人时,Redis相关问题的出题思路基本围绕五条线:
- 第一是基础功底,数据类型、持久化、过期淘汰,这个答不上来基本就可以结束了;
- 第二是缓存三兄弟和一致性,这属于线上实战题,没有真实处理经验很容易露馅;
- 第三是分布式特性,锁、集群、哨兵、脑裂,这些考察的是你系统设计的深度;
- 第四是底层原理,SDS、跳表、IO多路复用,考察的是你有没有研究过源码;
- 第五是工程化和治理,序列化选型、热Key处理、监控告警,考察的是你的运维意识。
这五条线基本覆盖了从校招到高P的绝大多数Redis问题。你能答到哪一层,直接决定面试官对你定位的上限。我带过不少候选人,最可惜的就是基础题背得溜,一追问细节就变成“我记得好像是……”,这种回答比直接说“没研究过”更败好感。
1.2 不同年限、不同岗位,准备重心不一样
面试官的题海不是随机的,而是跟着简历走的。3年以下的候选人,我基本只问基础加缓存三兄弟;3到5年的,我会追问锁和一致性的细节;5年以上的,我重点问治理和极端场景——热Key打爆了怎么降级、大Key删不掉怎么处理、集群扩容时数据怎么迁移。
如果你面试时发现对方问的问题超出了你的预期年限,先别急着觉得是压力面,大概率是你简历上写了相关项目。简历上写过的Redis相关内容,必须往死里抠细节,这是我见过最多人翻车的地方。比如你在项目里写了“使用Redis做分布式锁”,那就得把setnx、过期时间、看门狗、误删别人锁这些细节全部准备到位。
2. 基础篇:数据类型、持久化与淘汰策略
2.1 14道基础题自测清单
基础题属于“基本功”,如果答起来卡顿,先别急着面试,回去补课。先过一遍清单:
- Redis为什么这么快?答案至少要涵盖内存、IO多路复用、单线程、高效数据结构四点。
- Redis是单线程还是多线程?注意6.0之后IO读写有多线程,命令执行仍然单线程。
- String的底层结构是什么?SDS,为什么要用SDS而不是C字符串。
- List、Hash、Set、ZSet各自适合什么场景?排行榜、去重、缓存对象、简单消息队列。
- Redis为什么用跳表实现ZSet而不是红黑树?
- RDB和AOF有什么区别?什么时候用哪种,能混合吗?
- AOF重写是什么?为什么要重写?怎么触发?
- Redis的过期删除策略是什么?惰性删除加定期删除。
- 内存淘汰策略有哪几种?至少说出LRU和LFU的区别。
- 手写一个LRU怎么做?LinkedHashMap或者哈希表加双向链表。
- 数据明明设置了过期时间,为什么还占着内存?过期key没被访问也没被清理。
- String最大能存多大?512MB。
- Redis默认有16个库,能随便用吗?答案是不能,生产环境基本只用一个库。
- 线上Redis键太多,怎么快速扫描?keys慎用,大库用scan。
这几道题我几乎每次面试都会问到。别看简单,很多人答完第一题就开始飘,后面一追问细节就露馅。
2.2 高频题深挖:为什么快、单线程、数据结构
第一题的“为什么快”是面试官最常用的开场问题,也是区分背诵型和实战型的分水岭。老实说,纯内存是根本,IO多路复用解决的是网络瓶颈,单线程避免的是竞态和上下文切换,高效数据结构解决的是操作时间复杂度。如果你想体现深度,建议主动提一句“Redis 6.0引入了多线程IO,但命令执行依旧单线程,因为瓶颈在网络IO而不是CPU”这句话一说,面试官大概率会往下追问,这时候你把线程模型讲清楚,印象分会明显不一样。
第3题关于String底层,很多人知道SDS,却说不清好在哪。核心就三点:O(1)获取长度、杜绝缓冲区溢出、减少修改字符串时的内存重分配次数,另外二进制安全。面试的时候最好用“C字符串读长度是O(n)、拼接可能溢出”来反衬SDS的价值。ZSet为什么用跳表不用平衡树,也常被追问,跳表实现简单、范围查询方便,而且层数随机化,在并发写场景比红黑树好实现得多。
第4题数据类型,最容易答成背定义。面试官听你背完“String是字符串、List是列表”没有任何意义。更好的答法是带场景:String存对象和计数器,List做简单的消息队列,Hash存商品详情这种对象字段经常变动的数据,Set做用户标签去重,ZSet做排行榜和延迟队列。每个数据结构都说出至少一个业务场景,面试官才知道你真的用过。
2.3 过期与淘汰:不起眼却最能看出深浅的两道题
第8题过期策略,最标准的答法是“惰性删除+定期删除”:每次访问key时检查是否过期,过期就删;同时后台每隔一段时间抽取部分key检查删除。我习惯在答完之后补一句“主从复制场景下,过期key删除是靠主节点发DEL命令通知从节点,而不是从节点自己判断”,这句话能体现你真正看过Redis源码或者排查过缓存不一致,面试官会记下这个加分点。
第9题内存淘汰,先把8种策略说全,再谈生产环境怎么配。重点区分:volatile开头的策略只淘汰设置了过期时间的key,allkeys开头的对所有key生效;LRU是最近最少使用,LFU是频率最低优先淘汰。面试官经常追问“为什么Redis 4.0引入LFU”,因为LRU在扫描式遍历的场景下会被一次性流量污染,比如一个大促活动页瞬间访问大量商品,LRU可能把真正长期热门的key挤掉,LFU能更好地识别真正的热点。另外提一下,4.0之前默认noeviction,写满直接报错;现在更推荐把maxmemory-policy配成allkeys-lru,但热点明显的场景建议用LFU保护关键数据。
3. 缓存三兄弟与一致性:线上翻车高发区
3.1 12道进阶题清单(缓存与一致性)
这一部分最容易分出高下,因为它们都来源于线上事故。先过清单:
- 缓存穿透是什么?如何预防?
- 缓存击穿和穿透有什么区别?
- 缓存雪崩是怎么发生的?怎么解决?
- 布隆过滤器原理是什么?有什么缺点?
- 缓存和数据库双写时,先更新谁?为什么?
- 延迟双删是怎么实现的?能保证强一致吗?
- 什么场景下删除缓存失败?怎么补救?
- 缓存预热怎么做?上线时如何避免第一波流量压垮数据库?
- 多级缓存怎么设计?Redis之外还有哪些层?
- 空值缓存要注意什么?
- 缓存倾斜和热Key有什么关系?
- 如何保证最终一致性?消息队列在缓存一致性里扮演什么角色?
3.2 三兄弟场景拆解:别把答案背成一锅粥
穿透、击穿、雪崩,这三个词很多人背得滚瓜烂熟,但一问到“你们线上怎么处理的”,就变成背书。给一个场景化的答法:
穿透是查询了根本不存在的数据,比如用一个不存在的user_id去查,每次都会打到数据库。解决的核心思路是挡住“空”:一是缓存空值,设置一个较短的过期时间;二是用布隆过滤器,用极小内存成本挡住大量无效查询。这里要提一句布隆过滤器存在误判率,而且不支持删除,所以更精细的业务可以用布谷鸟过滤器,不过面试能说到误判率这一点已经合格了。
击穿说的是某个热点key过期瞬间,大量请求同时打到数据库。两个经典方案:互斥锁和逻辑过期。互斥锁很简单,缓存失效时只放一个线程去数据库加载,其他线程等待;逻辑过期是缓存里不设置物理过期时间,而是存一个过期标记,发现过期就异步去刷新。如果面试官追问区别,就说互斥锁会有一小段时间的阻塞和等待,逻辑过期能保证接口始终有数据,但会短暂返回旧数据,需要业务接受。
雪崩是大量key同时过期,或者Redis本身宕机。区分开这两个原因是加分项:前者通过过期时间加随机值、集群分片打散;后者靠哨兵或Cluster做高可用,再加本地缓存兜底,也就是多级缓存。能主动讲出“过期时间不要用固定值,用3600加上随机0到300秒”的人,说明真的处理过缓存雪崩。
3.3 缓存一致性:延迟双删到底删几次
第19、20、21题是最近几年的“网红题”。先说结论,没有完美的同步强一致方案,现实里追求的是最终一致。
最正统的Cache Aside模式:读时先读缓存,没有就查数据库再回填;写时先更新数据库,再删除缓存。为什么删除而不是更新缓存?因为更新缓存有概率把写一半的脏值写进去,而且如果写操作频繁,缓存会积累大量无效写;删除的话,下次读再回填,写操作本身不会污染缓存。
延迟双删是删除缓存后等一段时间再删一次,核心是处理“先删缓存、然后并发读写导致缓存被回填脏数据”的情况。但很多人理解错了一点:延迟双删并不能保证强一致,它只是把不一致窗口缩小。真正想兜底,得靠binlog订阅或者消息队列异步删除,删除失败时重试。我面试喜欢听到的回答是:“延迟双删在绝大多数场景够用,但如果对一致性要求真高,我会用canal监听binlog,解析出变更后删缓存,确保删除操作有重试机会。”
第24题空值缓存,最容易忽略的细节是:空值也要设置过期时间,而且要比正常数据短,否则大量不存在的数据会把Redis内存打爆;另外还要防止“空值过期后请求再穿透”的振荡,可以考虑在空值上再加一层短TTL。第22题缓存预热,别只答“写个脚本提前灌数据”,要答出预热前的容量评估、key分布、过期时间分散,以及预热过程中如果服务已经上线,要对第一波流量做兜底,比如限流、降级、熔断配合着用。
3.4 热Key与大Key:治理能力才是高级感
第25题热Key,典型场景是秒杀、热门直播。应对手段从轻到重可以这样讲:本地缓存兜底热点、Redis集群把热Key散列到多个节点、给逻辑层做限流和降级。面试时最好的回答是结合你自己的项目,说清楚“当时怎么发现热Key的”,比如监控到Redis单分片CPU飙高,或者抓包看到大量GET同一个Key。
大Key问题更隐蔽,一个Big List几百万元素,del的时候直接阻塞Redis,这在生产上是事故级的问题。处理办法:首先要发现,用slowlog、redis-cli --bigkeys先扫一遍;然后拆分,把大List改成分片,或者把大Hash拆成多个Hash;删除时不要直接del,用unlink异步删,或者渐进式删除(hscan加hdel分批删)。这一整段回答如果能带出“我曾经因为大Key导致业务超时”的案例,含金量会直线上升。
4. 分布式锁、集群与高可用
4.1 12道分布式高可用题清单
对经历过生产环境的人来说,这里才是Redis面试的硬核区。先列清单:
- 分布式锁用Redis实现,核心命令是什么?
- setnx加expire能保证原子性吗?
- 锁的value为什么要唯一?
- 锁过期了但业务还没执行完怎么办?
- Redisson的看门狗机制了解吗?
- RedLock是什么?为什么有人批判它?
- Redis主从复制是同步还是异步?丢了数据怎么办?
- 哨兵机制的工作原理是什么?
- Cluster集群的哈希槽是怎么分配的?
- 集群为什么用16384个槽?
- Cluster模式下scan、批量操作为什么有问题?
- 脑裂是如何产生的?Redis怎么尽量规避?
4.2 分布式锁:从setnx到Redisson的进化
第27、28题几乎是必问。早期的实现是setnx加expire两条命令分开写,这是经典的原子性问题——如果setnx成功但expire没执行,锁永远不释放。所以至少要用set key value nx ex seconds把两条操作合并成一条。这是最普通的答法。
想拿高分,要往下说三层。第一,value必须唯一,比如UUID或业务ID,释放锁时用Lua脚本先比较value再删除,防止误删别人的锁。第二,锁过期时间怎么定?设置得太短业务没跑完锁就断了,设置得太长万一宕机又要等很久,所以要有续期机制,这就是Redisson的看门狗——默认30秒,每10秒续期一次,业务执行完主动释放。第三,Redis主从异步复制会导致锁丢失,比如主节点写入了锁还没同步给从节点就宕机了,从节点顶上来,别的线程又能抢到同一把锁,所以RedLock才想用多个独立节点来投票。
但同时要说清楚,RedLock在业内是有争议的,它对系统时钟、网络分区有依赖,很多团队宁可zookeeper也不用RedLock。能把争议说清楚,比盲目吹RedLock高级得多。面试官问到这里基本就知道你是真用过Redis做锁,而不是只看过博客。
4.3 主从、哨兵与集群:可用性三道坎
第33题主从复制,记住主从之间的数据复制默认是异步的,所以主节点刚写完就宕机,数据在从节点上可能还没到,这会导致数据丢失。Redis从2.6开始推荐主节点开启min-replicas-to-write,从节点数或者延迟不满足要求时主节点拒绝写请求,用可用性换可靠性。这个细节能看出你认真配置过生产Redis。
第34题哨兵,核心讲清楚三个角色:监控、通知、自动故障转移。哨兵节点之间通过投票选出leader来执行故障转移,然后从候选从节点里选master,标准通常是优先级、复制偏移量、runid。另外提醒一个容易被忽略的点:哨兵至少三个、奇数个,防止脑裂后投票无法达到多数。
第35、36题Cluster,最关键是哈希槽的概念。16384个槽,每个key做CRC16再对16384取模,落到某个槽,槽分布在多个主节点上。为什么不直接用一致性哈希?因为槽的设计让数据分布和迁移都更好控制。至于为什么是16384,可以提一个说法:16384是2的14次方,足够大,又不会让心跳包太大,实际使用中节点数很难超过这个数量级。
第37题是实战题,Cluster模式下scan只能保证单节点内部的一致性,跨节点扫描会有重复和遗漏;mset、mget这类多key操作如果key不在同一个槽,直接报错。这时候要用hash tag,比如user{1}:profile和user{1}:orders,让同一业务的多key落在同一个槽。能主动提到hash tag的人,基本是有过集群迁移经验的。
5. 底层原理与实战收官
5.1 最后的12道题
前38道题已经覆盖了绝大部分高频考点,这里再补上最后一批,也是最能拉开区分度的一批:
- 为什么Redis用跳表而不用红黑树做ZSet?
- 渐进式rehash是怎么个渐进法?
- Redis的IO多路复用为什么比多线程好?
- Redis 6.0多线程IO到底改了啥?
- 内存碎片产生的原因?怎么处理?
- Redis的慢查询日志怎么配置?怎么定位慢命令?
- 线上Redis内存突然飙升,怎么排查?
- Redis和数据库的序列化方式怎么选?
- 为什么生产环境建议关闭keys命令?
- Redis可以做消息队列吗?和专门MQ的区别?
- 缓存key怎么设计才清爽?
- 如何复习Redis面试题,才能避免背了忘、忘了背?
5.2 底层原理:三道必答源码题
第39题跳表和红黑树的对比,重点在于范围查找。红黑树虽然也是O(logN),但它的中序遍历需要递归或栈,范围查询实现复杂;跳表按层索引做范围查找非常自然,而且插入删除只需要调整前后指针,并发场景下比红黑树更容易实现。再加上Redis本身只需要排序和范围操作,不需要删除最大最小值等复杂操作,跳表是更务实的选择。
第40题渐进式rehash,这是哈希表扩容避免阻塞的关键。简单说就是rehash不是一次性完成,而是分多次,每次增删改查时顺带迁移一些桶,同时维护两个哈希表,查询时先查正在使用的,再查迁移中的。答这个问题要补一句“如果一次性rehash大量数据,Redis主线程会卡顿”,这就带出了单线程模型的取舍。
第41题IO多路复用,把epoll的机制讲清楚:内核帮你监听一堆fd,哪个fd可读可写了,再通知你处理。Redis把事件循环封装成一个主线程,连接事件、读写事件、定时任务都在这一个线程里轮询。核心思考点是:Redis的瓶颈从来不是CPU,而是网络IO和内存,所以用单线程加IO多路复用是最省事的方案。如果面试官追问“为什么6.0又加多线程”,那是因为Redis每秒能处理十万级命令,网络协议解析和IO读写消耗开始占据大头,把这些部分放到多线程里,主线程专注命令执行,收益才足够明显。
5.3 工程治理:内存飙升、序列化与消息队列
第44、45题,慢查询和内存排查,属于一定要讲案例的题。我一次线上排查,Redis内存从2G涨到6G,一开始怀疑是业务缓存泄漏,后来用redis-cli --bigkeys扫了一遍,发现是一个大Hash忘记设置过期时间,而且某个字段一直在被高频写入。处理方式很简单:给业务key加过期时间,把这个Hash按业务id拆掉。这种案例在面试里非常加分,因为比任何理论都更能证明你处理过真实问题。
第46题序列化,很多人踩过坑。默认的JdkSerializationRedisSerializer会把对象序列化成带有类全名的二进制,可读性差、内存占用高,换成JSON之后内存直接降了一半。面试官喜欢追问“为什么有的对象序列化成JSON会报错”,因为对象里有循环引用或者没有默认构造函数,所以生产上更推荐GenericJackson2JsonRedisSerializer加手动指定class,或者干脆用protobuf。
第47题keys命令是典型的反模式,因为keys会遍历整个键空间,单线程下所有请求都会被阻塞。替代方案是scan,但scan不能保证完整不遗漏,适合分批处理大量key的场景。答这道题记得用“线上事故”来佐证:同事在2亿key的实例上执行keys,Redis直接卡了十几秒,这比任何理论都有说服力。
第48题Redis做消息队列,要区分版本:List的BLPOP是早期的队列模式,发布订阅方式不持久化,Stream则是从Redis 5.0开始提供的正式消息队列能力,支持消费者组、ACK、Pending。但和RabbitMQ、Kafka相比,Redis的短板是消息堆积后的内存压力、不擅长海量堆积消息,所以适合轻量级场景。
5.4 收尾:答题的姿势比答案本身更值钱
最后想说的是,第49题和第50题其实是给自己做复盘用的。缓存key设计得好,线上能少掉一半问题,我有一次接手别人的项目,发现所有缓存key都长成“xxx_yyy_zzz”,没有统一前缀,排查问题非常痛苦。后来统一改成“业务:模块:对象:id:场景”,比如“user:profile:9527:summary”,一眼就能看出是哪个业务、哪个对象、哪个字段。
至于第50题,怎么复习才不容易忘?我自己带人的经验是:不要按题号背,要按“故事”背。把缓存穿透的布隆过滤器、缓存击穿的互斥锁、缓存雪崩的随机过期时间串成一个完整的“缓存治理”叙事;把从setnx到看门狗到RedLock的演进串成“分布式锁”叙事;把主从、哨兵、Cluster串成“高可用”叙事。面试官问哪个,你都能从故事里抽出来讲,而不是从记忆里抽出来背。这样哪怕临场紧张,也能说出自己真正做过、思考过的内容。