
面试Redis翻来覆去问的就那几张牌。最近帮几个朋友做了几轮模拟面试又翻了一遍近两年大厂的Redis真题发现一个规律真正的高频题翻来覆去就是那二三十道。什么底层数据结构、持久化、缓存穿透、分布式锁、主从集群换个问法反复出现。背答案没用你得把背后的原理和取舍讲清楚面试官才认。这篇文章把我整理的高概率Redis面试题和参考答案完整列出来每道题都附带答题思路和面试官可能的追问方向。全文按主题拆成几个大块建议按顺序读因为很多题之间是关联的比如你不懂过期策略缓存雪崩那道题就答不到点子上。1. Redis凭什么快IO模型与单线程面试坑1.1 单线程为什么还能这么快这是Redis面试第一题出现概率接近百分之百。很多人张口就答因为基于内存但面试官要听的可不止这一句。完整答题思路分四层第一层内存存储。Redis数据全在内存里读写不涉及磁盘IO寻址和访问速度在纳秒级。这是性能的根基MySQL要查B树、要走磁盘IO天然差了几个数量级。第二层IO多路复用。Redis用的是基于epoll的事件驱动模型一个线程同时监听成千上万个socket连接。内核会主动通知哪些socket可读可写Redis只处理有事件发生的连接不会傻等任何一个客户端。这就像餐厅里只有一个服务员但他记性好知道哪桌在招手、哪桌在喊结账按顺序挨个服务比每个桌子配一个服务员多线程阻塞IO效率反而高。第三层高效的数据结构。Redis每种数据类型底层都有精心设计的编码结构后面会细讲。第四层单线程避免了上下文切换和锁竞争。多线程编程最头疼的是什么共享资源的并发访问要加锁加锁就有竞争和阻塞。单线程模型天然没有这个问题。而且单线程也简化了命令执行的原子性——一个命令从开始到结束不会被其他命令打断。1.2 6.0引入的多线程是怎么回事面试官很爱追问那Redis 6.0为什么引入多线程不是说单线程好吗这是个经典陷阱题。Redis 6.0的多线程只用来处理网络IO的读写和协议解析真正的命令执行依然是单线程。原因很实在随着网络硬件升级万兆网卡出现了单线程处理网络数据包成了瓶颈。一个客户端发来一条命令从内核缓冲区拷贝数据、解析协议这些操作占用了大量CPU时间而这些操作不带任何共享状态完全可以用多线程并行。打个比方餐厅的服务员主线程负责点菜下单现在客人太多点菜记录纸都写不过来所以就安排几个专门跑腿传菜的人IO线程先把菜单从前台送到后厨但真正决定做什么菜、怎么做还是主厨主线程一个人说了算。回答时把这几层讲清楚基本能镇住场子。2. 数据类型底层实现从使用场景到内部编码2.1 五种基础类型的适用场景这是Redis面试必问题而且经常开始于你用过Redis的哪些数据类型这样开放式的题干。可以把场景说得贴合实际项目String字符串最常用缓存用户信息、计数器、分布式ID、Session共享。底层可以是int整数、embstr短字符串或raw长字符串。面试可以提一嘴如果value是数字INCR/DECR是原子操作秒杀库存扣减可以直接用。Hash哈希适合存对象比如用户信息、商品信息。优势是可以单独读写某个字段不用整个对象序列化。底层是ziplist或hashtable。在缓存用户资料时我一般首选Hash改昵称只需要HSET一个字段不用像String那样把整个JSON读出再写回。List列表底层是quicklist3.2之前是ziplistlinkedlist。典型场景是消息队列、时间线、最新文章列表。LPUSH BRPOP可以做成一个简单的可靠队列。但注意现在生产环境做消息队列一般用Stream类型或者专门的MQList队列功能太简陋没有消费确认和消息回溯机制面试时别把它吹成可以替代Kafka。Set集合底层是intset或hashtable元素唯一且无序。适合做去重、共同关注、抽奖SRANDMEMBER。经典题统计UV用PFADDHyperLogLog和用Set有什么区别Set精确但占内存HyperLogLog有0.81%的标准误差但省内存。UV量级千万以上HyperLogLog是标准答案。ZSet有序集合底层是skiplist hashtable。适合排行榜、延时队列score存时间戳。ZADD、ZREVRANGE是排行榜标配。搜热搜榜实时排名怎么做的答案基本就是ZSet。2.2 跳表为什么不用红黑树ZSet底层那个skiplist是深挖重灾区。面试官会问为什么用跳表而不用红黑树回答要点实现简单跳表就是一个多层级的有序链表通过随机化决定节点层数代码量比红黑树少一个量级不容易写错。区间查找友好ZSet的核心操作是ZRANGEBYSCORE需要按score范围取数据。跳表是链表结构找到起点后向后遍历就行。红黑树是中序遍历范围查找虽然也支持但实现复杂度明显更高。性能等价跳表的查找、插入、删除平均时间复杂度都是O(logN)和红黑树打平。既然性能一样、实现更简单没有理由选红黑树。顺便记一个具体数字跳表最高64层每个节点层数是1/2概率往上加一层所以p1/2这和Redis源码里的ZSKIPLIST_P是一致的。2.3 底层编码是个加分项高手和新手的区别就在于高手会主动聊编码转换。比如Hash类型元素少、值小的时候用ziplist紧凑内存超过hash-max-ziplist-entries默认128或hash-max-ziplist-value默认64字节自动转hashtableZSet用ziplist还是skiplistSet用intset还是hashtable都有类似阈值。这些配置项在redis.conf里能改。这一层是我深入看过源码结构和内存优化的信号面试官会把你的level往上调一档。3. RDB与AOF持久化不能只背口诀3.1 RDB触发时机和Pod里常见的坑RDB是内存快照二进制格式触发方式分手动SAVE/BGSAVE和自动配置文件里的save规则。核心要掌握SAVE是阻塞的生产环境禁用。BGSAVEfork子进程生成快照主进程继续服务。fork是写时复制COW子进程刚fork那一刻和父进程共享内存页后续父进程改了哪些页哪些页才复制。这就是RDB对性能影响小的原理。面试题里还有个容易踩的坑save 900 1是什么意思表示900秒内至少有1次写操作就触发BGSAVE。三个save配置只要满足任意一个就触发。实际生产中RDB做备份和一些场景的快速恢复但有两个硬伤一是快照之间有数据丢失窗口二是fork大实例时可能卡顿。Redis官方对数据安全要求高的场景建议RDBAOF混合用。3.2 AOF的三种写回策略AOF是把每一条写命令追加到文件末尾相当于记录操作日志。三种appendfsync策略策略写回时机数据安全性性能影响always每条命令都fsync最安全最多丢一条最慢吞吐量大幅下降everysec每秒fsync一次最多丢1秒数据性能影响可接受no由操作系统决定何时刷盘可能丢较多数据最快生产环境默认everysec。always只在对数据安全极度敏感的场景用——我见过有人把always当成完全可靠但实际上Redis进程本身崩溃还好如果服务器断电always也保证不了绝对不丢数据。追问点来了AOF文件越来越大怎么办答AOF重写。Redis会fork子进程将当前内存中的数据重新生成一条条写命令生成一个新的AOF文件替换旧文件。注意重写不是对旧文件做压缩而是基于当前内存状态生成一套最精简的写命令。重写期间新到的写命令会同时写入AOF重写缓冲区防止丢数据。3.3 混合持久化和数据恢复顺序Redis 4.0以后的混合持久化可能是面试加分题开启aof-use-rdb-preamble yes后AOF文件头部是RDB格式的二进制快照后面追加增量写命令。好处是重启恢复时加载RDB部分快、再回放少量增量命令比从头回放整个AOF日志快得多。数据恢复顺序务必记住Redis启动时优先加载AOF文件如果AOF不存在再加载RDB。因为AOF数据完整度更高。如果两者都配了以AOF为准。有次线上事故我印象很深某团队手动执行BGSAVE把RDB刷得很新但AOF因为everysec策略晚了几秒结果重启后Redis加载了还没来得及刷盘的AOF数据反而回退了。这正好说明为什么官方文档强调想用RDB做恢复就别同时开AOF或者接受谁在后谁为准的复杂性。4. 缓存三大难题穿透、击穿、雪崩的完整救助方案4.1 缓存穿透查询一个不存在的数据穿透的特征请求的数据在缓存和数据库里都不存在每次请求都直接打到数据库。攻击者可以构造大量不存在的ID瞬间打死数据库。标准解法是布隆过滤器在缓存前面加一层Bloom Filter把所有可能存在的数据ID放进去。查询时先过布隆过滤器说不存在就一定不存在直接返回说存在才放行到缓存和数据库。第二种解法是缓存空值从数据库查到null也把这个key缓存起来TTL设短一点比如5分钟。下次同样的请求直接命中空值不再穿透到DB。缺点如果非法key是随机变化的空值缓存会塞满Redis需要配合key格式校验。回答时把两种方案都讲了再补一句布隆过滤器有误判率它说存在不一定真存在说不存在一定不存在。所以它只能挡住确定不存在的key挡不住存在但缓存过期的击穿场景——这个问题正好引出下一题。4.2 缓存击穿热点key突然过期击穿和穿透一字之差本质完全不同。击穿是某个热点key在缓存过期的瞬间大量并发请求同时打到数据库。比如微博热搜第一的词条缓存过期一瞬间几十万请求进来数据库扛不住。两个主流解法互斥锁分布式锁当缓存没命中时不是所有线程都去查数据库而是先尝试获取锁只有拿到锁的线程去查库并回填缓存其他线程等待一段时间后重新查缓存。伪代码public String get(String key) { String value redis.get(key); if (value null) { if (redis.setnx(lock: key, 1, 10, TimeUnit.SECONDS)) { try { value db.query(key); redis.set(key, value, 5, TimeUnit.MINUTES); } finally { redis.del(lock: key); } } else { Thread.sleep(100); return get(key); // 自旋重试 } } return value; }注意setnx必须带上过期时间防止拿到锁的线程宕机导致死锁。逻辑过期不给key设置物理过期时间而是在value里存一个逻辑过期时间戳。每次读的时候做校验如果逻辑过期了返回旧值的同时异步去数据库更新缓存并刷新时间戳。这种方案的好处是读请求不会阻塞缺点是短暂的数据不一致。分布式锁和逻辑过期面试官一定会让你对比。前者一致性更强但可能阻塞短暂时间后者高可用更好但容忍短暂脏读。我一般说看业务容忍度秒杀、支付对一致性要求高选互斥锁Feed流、商品详情选逻辑过期。4.3 缓存雪崩大面积key同时失效雪崩有两种情况一是大量key设置了同一个过期时间在同一时刻集体失效二是Redis本身宕机。针对第一种解法TTL加随机值。比如原来是1小时改成1小时±随机5分钟避免齐刷刷过期。多级缓存本地缓存Caffeine/Caffeine CacheRedis本地缓存挡第一波。热点key分散到多个节点冗余存储。针对Redis宕机解法搭建主从哨兵实现故障自动切换。限流降级比如Sentinel或Hystrix数据库扛不住时直接返回兜底数据。提前做好持久化恢复时快速加载。有一个容易被忽略的细节预热。上线大促活动前提前把热点数据刷进缓存而不是等用户请求触发回填。很多雪崩其实发生在活动刚开始的瞬间因为所有人同时首次访问缓存全部miss数据库直接被打穿。预热就是提前把缓存喂饱。5. Redis分布式锁从setnx到Redisson的完整演进5.1 分布式锁的底层原理和误删问题分布式锁面试出现率极高尤其Java岗位。最原始写法Boolean result redis.setnx(key, value); if (result) { // 执行业务 redis.del(key); }这个写法有三个致命问题问题一死锁。拿到锁的线程中途挂了锁永远不会释放。解法setnx的时候必须加上过期时间。但setnx和expire两条命令组合不是原子操作中间挂了照样出问题。所以要用原子的SET key value NX EX 30一条命令搞定。问题二误删别人的锁。线程A处理时间太长锁过期自动释放了。线程B拿到锁开始执行。此时A处理完了执行del把B的锁删了。解法value存一个唯一标识比如UUID删除前先比较再删除还得保证比较和删除是原子操作用Lua脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end问题三业务执行时间超过锁过期时间。锁自动释放了其他线程进来A还没执行完数据就乱了。解法Redisson的看门狗机制默认每10秒检查一次如果锁还在就自动续期到30秒。说白了就是个后台定时任务给锁续命业务跑多久锁就续多久。5.2 主从架构下的锁失效问题与RedLock争议高级面试题会问主从架构下客户端A在主节点上拿到了锁主节点还没把锁数据同步到从节点就宕机了哨兵把从节点提升为主节点此时客户端B在新主节点上也能拿到同一把锁。两个客户端同时持锁分布式锁就形同虚设了。RedLock的思路是向集群中大多数N/21独立节点依次申请锁如果超过半数节点成功才算获取锁成功。这样即使一个节点挂了其他节点还有锁记录新人拿不到。但RedLock在业内一直有争议。反对方的观点集中在这会让系统变慢要同时访问多个Redis而且一旦发生GC停顿或时钟跳跃依然可能出现多个客户端同时持锁。我个人在生产上一般不用RedLock而是把核心判断让给数据库兜底或者选择具备强一致性的分布式协调组件做关键场景的锁。如果面试官问RedLock客观说出它的思路和争议点就够了不要把他当银弹。6. 主从同步、哨兵与集群高可用架构怎么答才显功力6.1 主从复制的完整过程题目通常是Redis主从复制原理是什么。关键是讲清楚全量复制和部分重放。全量复制发生在从节点第一次连接主节点或者主从断线太旧导致增量数据丢失的情况从节点向主节点发送PSYNC请求。主节点执行BGSAVE生成RDB快照。主节点将RDB文件发送给从节点。从节点清空旧数据加载RDB。主节点把BGSAVE之后的写命令存放在复制积压缓冲区等RDB发完后再发给从节点。这个过程里最容易翻车的点是大实例全量复制会阻塞。BGSAVE fork子进程时主进程页表会短暂阻塞RDB传输占带宽从节点加载RDB时不能服务读请求。几GB的内存实例做全量复制可能造成秒级甚至更长的卡顿。部分重放是断线重连后的增量同步主节点维护一个复制积压缓冲区默认1MB可配repl-backlog-size从节点带着自己的repl_offset来请求主节点判断offset是否还在缓冲区内在就只发送差额数据。不在就退化为全量复制。这里面试官经常会挖一个坑从节点收到的命令是异步的吗答案是主从复制默认是异步的主节点执行完写命令立即返回不等待从节点ACK。所以主从永远有短暂的数据不一致。Redis官方在较新版本里也提供了WAIT命令可以阻塞等待至少N个从节点确认但生产环境极少用它因为性能代价太大。6.2 哨兵模式怎么完成自动故障转移哨兵的核心职责监控、通知、自动故障转移。具体流程每个哨兵节点每秒发送PING检测主从节点的健康状态。如果一个哨兵发现主节点超过down-after-milliseconds没响应标记为主观下线SDOWN。哨兵之间通过互相通信确认如果足够数量的哨兵都认为主节点下线标记为客观下线ODOWN。触发故障转移哨兵集群选出一个LeaderRaft算法从从节点中选举一个新主节点。其他从节点改为复制新主节点客户端也会被通知新主节点地址。面试必问新主节点怎么选出来的选举依据依次是过滤掉断线、超时的从节点过滤掉没有复制过主节点数据的从节点。按优先级slave-priority排序配置值越小优先级越高。优先级相同比较复制偏移量repl_offset谁的数据越新选谁。再相同比较run_id小的胜出。6.3 Redis Cluster的哈希槽为什么不直接取模Cluster模式用16384个哈希槽每个key通过CRC16(key) % 16384算出落在哪个槽节点负责一部分槽位。增删节点时只需要迁移槽位不需要重新哈希所有数据。这里面试官可能问为什么槽数是16384不是65536我的理解是16384个槽足够支撑集群规模官方推荐节点数不超过1000再大的话心跳包里的位图就会变大。每个节点心跳包要用bitmap标记自己负责哪些槽65536个bit是8KB16384个bit是2KB心跳频繁每秒PING/PONG太大浪费带宽。另外节点故障时槽位迁移的复杂度也不用那么大。Cluster模式下客户端访问key时如果key不在当前节点节点会返回MOVED错误带目标节点地址客户端需要重定向。这里很多人会再补一句客户端通常需要维护一份槽位映射表智能客户端比如JedisCluster、Lettuce会缓存槽位信息避免每次都重定向。7. 过期删除与内存淘汰容易混淆的一组底层题7.1 过期key是怎么删除的Redis对设置了TTL的key删除策略是惰性删除定期删除的混合。惰性删除每次读取key时检查是否过期过期就删掉再返回空。好处是对CPU友好坏处是过期key如果一直没被访问就一直在内存里躺着。定期删除Redis每隔一段时间默认每秒10次左右随机抽取一批设置了TTL的key检查是否过期过期则删除。注意是随机抽一批不是全量扫描。否则几千万个key全扫一遍性能就炸了。这里面试官容易深挖定期删除具体怎么抽Redis会维护一个全局的过期key字典每次抽查时随机拿20个删掉过期的。如果过期比例超过1/4说明过期key比较多再抽一轮。这个逻辑在源码的activeExpireCycle函数里答出这个细节会非常加分。7.2 内存满了怎么办8种淘汰策略当Redis内存达到maxmemory限制时根据maxmemory-policy执行淘汰。必须把8种策略完整记住最好能说出各自的适用场景策略作用范围行为noeviction所有key达到上限后写命令返回错误allkeys-lru所有key淘汰最近最少使用的keyvolatile-lru设置过TTL的key淘汰最近最少使用的keyallkeys-random所有key随机淘汰volatile-random设置过TTL的key随机淘汰volatile-ttl设置过TTL的key淘汰剩余寿命最短的keyallkeys-lfu所有key淘汰最不经常使用的keyvolatile-lfu设置过TTL的key淘汰最不经常使用的key生产环境最常用的是allkeys-lru或allkeys-lfu。LRU适合大多数缓存场景LFU更适合访问频率极不均衡、热key非常集中的流量模型。两者的实现细节也有考点Redis的LRU是近似LRU不是精确LRU。精确LRU要为每个key维护时间戳链表内存开销太大。Redis用抽样默认抽5个选最旧的淘汰在maxmemory-samples配置里调。7.3 缓存和数据库一致性怎么做这不是纯Redis题但往往在同一轮面试里被抛出来常见问法更新数据库和删除缓存顺序怎么定标准答案先更新数据库。再删除缓存。为什么不是先删缓存再更新数据库因为删缓存后、更新数据库前有请求过来发现缓存miss读到数据库旧值回填缓存就变成旧值了后续读一直命中旧值。为什么不是更新缓存而是删除缓存因为更新缓存涉及并发写、增量更新复杂删掉缓存让下次读的时候重建更简单安全。惰性加载天然规避了很多一致性问题。删缓存失败怎么办业界做法是订阅数据库的binlog比如Canal监听到变更后异步删缓存或者用消息队列做重试。如果要求强一致那就别用Redis缓存了直接在数据库层面解决问题——缓存本来就是用来扛读压力的不是用来保证强一致的。8. 面试场上怎么把知道变成分数讲到这儿题目本身已经过完了。但有几个面试技巧方面的经验想多说几句因为这些不是从文档里能学到的。第一回答面试题千万别按教科书顺序一条条背。面试官问Redis持久化怎么做的你要是上来就背RDB是什么、AOF是什么、混合是什么十有八九被打断。更好的方式是先给结论Redis支持RDB和AOF两种方式RDB侧重备份和恢复速度AOF侧重数据安全4.0之后可以混合使用。我项目里是这样配的……先给全景再等面试官挑一个细节深挖。这种回答方式在系统设计面试里叫先总体后细节在任何技术面都适用。第二主动暴露我踩过坑。比如聊主从复制顺嘴提一句之前生产上遇到过一次从节点全量同步导致主节点卡顿后来把repl-backlog-size调大又限制了同一时间只允许一个从节点做全量复制。面试官听的不是你背了多少文档而是你有没有真实操作过的痕迹。第三遇到不会的题别慌。Redis题目再偏基本都有迹可循。比如问你Redis的BigKey怎么发现和处理你要是没搞过可以坦诚说没专门排查过BigKey问题然后补充自己的解决思路用redis-cli的--bigkeys扫描或根据业务规范限制value大小。面试官看的是你面对未知问题的思路不是已知问题的答案。我见过太多人把面试当考试以为答案全对就能过。实际上技术面试在考察三件事基础扎不扎实、生产经验有没有、遇到问题怎么思考。这套题是帮你把基础这块地基打牢后面两块得靠平时真刀真枪地写代码、排查问题、复盘总结。Redis的知识点虽然多但核心也就这几块把底层原理想透比刷一百道偏题怪题管用得多。