Skip to content

Spring集成Redis面试题库 ​

生成时间:2026-05-22 22:49


【面试题】1. Redis ZSet底层编码什么时候用ziplist(listpack),什么时候用skiplist?为什么这样设计? ​

【标准答案】当元素数量小于128且所有元素长度小于64字节时,ZSet使用ziplist(Redis 7.0后为listpack)编码,否则转为skiplist+hashtable双结构。ziplist/listpack是紧凑的连续内存结构,省内存但查找是O(n);skiplist查找是O(logn),hashtable提供O(1)的分数查成员能力。这样设计是典型的"小数据省内存、大数据保性能"的权衡思路。面试中要强调:这不是固定阈值,可以通过zset-max-listpack-entries和zset-max-listpack-value参数调整。同时要提到listpack替代ziplist的核心原因——ziplist的连锁更新问题(prevlen字段导致一个节点扩容引发后续节点级联扩容),listpack用记录自身长度的方式解决了这个问题。

【面试题】2. AOF重写的原理是什么?重写期间有新写入怎么办?和RDB bgsave有什么区别? ​

【标准答案】AOF重写不是读取旧AOF文件来精简,而是fork子进程遍历当前内存中的数据,直接生成新的AOF命令。重写期间主进程继续处理请求,新写入的命令会同时追加到旧AOF缓冲区和重写缓冲区(aof_rewrite_buf)。子进程重写完成后,主进程把重写缓冲区的增量命令追加到新AOF文件末尾,然后用新文件原子替换旧文件,保证数据零丢失。和RDB bgsave的区别:(1) bgsave生成的是二进制快照,AOF重写生成的是文本命令;(2) bgsave期间的写操作靠COW机制保证一致性,AOF重写靠重写缓冲区;(3) AOF重写本质是对数据的"逻辑快照"——只保留最终状态的写命令,去掉过期key、重复set、已删除key的历史操作。面试加分点:Redis 7.0后AOF采用多部分文件格式(multi-part AOF),不再需要单文件重写,减少了替换期间的阻塞。

【面试题】3. Spring Data Redis中Lettuce和Jedis的核心区别是什么?生产环境怎么选? ​

【标准答案】三个核心区别:(1) 连接模型:Jedis是直连模式,每个操作需要一个连接,多线程下需要连接池;Lettuce基于Netty,一个连接可以复用,天然线程安全,不需要连接池开销;(2) 异步支持:Jedis只支持同步阻塞调用;Lettuce支持同步、异步(Future)、响应式(Reactive)三种模式;(3) 集群支持:Lettuce对集群拓扑刷新、读写分离、管道都支持更好。生产环境推荐Lettuce(Spring Boot 2.x后默认就是Lettuce)。但要注意:Lettuce长时间空闲连接可能断开,需要配置validateConnection=true或开启lettuce的心跳。Jedis适用场景:代码简单、只需要同步操作、对Netty依赖有顾虑的老项目。面试要补充:连接池配置上Lettuce用commons-pool2做连接复用池(不是传统连接池),配置max-active/max-idle控制并发连接数,避免OOM。

【面试题】4. @Cacheable的condition和unless有什么区别?什么场景下用哪个? ​

【标准答案】condition在方法执行前判断,决定是否走缓存(不满足则直接执行方法,不查缓存也不写缓存);unless在方法执行后判断,决定是否将结果写入缓存(方法一定执行,查缓存逻辑也走,只是结果不缓存)。典型用法:condition="#id > 100"——只对id大于100的查询走缓存;unless="#result == null"——空结果不缓存(防止缓存穿透可以反过来缓存空值,此时不用unless)。另一个常见场景:unless="#result.size() == 0"——空集合不缓存。面试关键点:condition是前置门控,unless是后置过滤。如果想在缓存穿透场景中缓存空值,就不能用unless过滤null,而是要主动缓存空对象或空字符串占位。

【面试题】5. Redis缓存和数据库双写一致性,Cache Aside模式有什么问题?延迟双删真的能解决吗? ​

【标准答案】Cache Aside(旁路缓存)的标准流程:读先查缓存→miss查数据库→写缓存;写先更新数据库→再删缓存。它的经典问题是:并发场景下,线程A更新数据库后删缓存之前,线程B读到旧数据库值写入缓存,导致缓存和数据库不一致。延迟双删的思路是:更新数据库→删缓存→延迟N毫秒→再删一次,希望第二次删除能覆盖线程B的脏缓存。但延迟时间怎么定?太短没用,太长浪费且不一致窗口大。实际上延迟双删只是降低概率,不能根本解决。真正靠谱的方案:(1) Canal监听binlog异步删缓存——最终一致性保证;(2) 设置较短的TTL兜底——即使删失败也会自动过期;(3) 写数据库和删缓存用消息队列保证顺序——强一致性要求时用。面试要明确:分布式场景下缓存和数据库的强一致性几乎不可能,我们追求的是最终一致性+业务可接受的不一致窗口。

【面试题】6. Redisson分布式锁的看门狗机制原理是什么?什么情况下看门狗会失效? ​

【标准答案】Redisson加锁时如果不指定leaseTime,默认锁超时30秒,同时启动一个看门狗(后台线程),每隔10秒(超时时间的1/3)检查锁是否还被持有,如果是就续期到30秒。这样业务没执行完锁就不会过期,执行完了主动释放看门狗就停了。看门狗失效的情况:(1) 手动指定了leaseTime——Redisson认为你明确控制超时时间,不启动看门狗;(2) 持有锁的客户端宕机——看门狗线程跟着死掉,锁在30秒后自动释放(这其实是正确行为);(3) 业务线程阻塞超过30秒且看门狗续期请求本身超时或失败——极端网络抖动场景。面试加分:Redisson锁的可重入实现用Hash结构,key是锁名,field是"客户端UUID:线程ID",value是重入次数。每次加锁value+1,释放-1,到0才真正删key。

【面试题】7. Redis Stream和Kafka相比,做消息队列有什么优劣?Spring Data Redis怎么用Stream? ​

【标准答案】Stream的优势:(1) 零依赖,不需要额外部署Kafka集群;(2) 支持消费者组、消息确认(XACK)、消息持久化,可靠性比Pub/Sub强太多;(3) 轻量级,适合中小规模场景。劣势:(1) 消息堆积能力有限,Redis是内存数据库,数据量大了成本高;(2) 不支持分区(Partition),单Stream吞吐上限受单节点限制;(3) 没有Kafka成熟的生态和运维体系。Spring Data Redis使用Stream:通过StreamMessageListenerContainer监听消息,@Bean配置容器,用StreamOffset.create("mystream", ReadOffset.lastConsumed())从消费者组未消费位置读取。发送用redisTemplate.opsForStream().add()。核心配置是消费者组的创建:XGROUP CREATE命令,如果不存在需手动初始化。面试要提:Stream的XREADGROUP + XACK保证了至少一次消费,但业务端需要自己做幂等。

【面试题】8. 用Redis实现滑动窗口限流,Lua脚本怎么写?和固定窗口限流有什么本质区别? ​

【标准答案】滑动窗口限流Lua脚本核心逻辑:(1) ZADD把当前时间戳作为score加入ZSet;(2) ZREMRANGEBYSCORE清除窗口外的旧数据;(3) ZCARD统计窗口内请求数;(4) 判断是否超限,超限返回0,不超限返回1并设置过期时间。关键是要用Lua保证原子性,否则并发下判断和写入之间有竞态。和固定窗口的区别:固定窗口以整分钟/整小时为边界,边界处突发流量可能达到2倍限制(比如59秒放100个、01秒又放100个,2秒内200个请求);滑动窗口以当前时刻为基准往前推一个时间窗口,统计的是真正的"过去N秒内"的请求数,不存在边界突变问题。面试加分:实际工程中常用简化版——用一个小时间片(如1秒)的固定窗口组合来近似滑动窗口,比如1分钟限流拆成6个10秒的子窗口,性能更好,精度可接受。

【面试题】9. Redis BigKey怎么发现?怎么拆分?对持久化和高可用有什么影响? ​

【标准答案】发现方式:(1) redis-cli --bigkeys扫描;(2) redis-rdb-tools分析RDB文件;(3) MEMORY USAGE key查看单个key内存占用;(4) INFO命令看used_memory和碎片率异常。BigKey标准:String超过10KB,集合元素超过5000个。拆分方案:(1) 大Hash→按field分片成多个小Hash,如user:info:{userId%16};(2) 大List→按时间或ID分页,拆成多个小List;(3) 大ZSet→按分数范围分片;(4) 大String→压缩或拆字段存不同key。对持久化的影响:BigKey导致RDB bgsave时COW内存翻倍(修改一个BigKey要整页复制);AOF重写时一个BigKey的写入命令很长,可能导致重写缓冲区暴增。对高可用影响:主从全量同步时BigKey传输慢导致同步延迟;Cluster迁移槽位时BigKey的MIGRATE命令可能超时失败;BigKey删除会阻塞主线程(DEL是同步操作,4.0后可用UNLINK异步删)。面试要强调:BigKey问题的核心是"单点大",解决思路是"分而治之"。

【面试题】10. Redis 7.x的Function机制和传统Lua脚本有什么区别?解决了什么问题? ​

【标准答案】传统Lua脚本的问题:(1) EVAL每次传输完整脚本,网络开销大;EVALSHA需要预加载,脚本事关丢失后要重新传;(2) SCRIPT FLUSH清脚本缓存导致所有SHA失效;(3) 脚本没有版本管理和持久化,重启后丢失;(4) 脚本没有命名空间,全局共享容易冲突。Function的改进:(1) Function是持久化的,写入RDB/AOF,重启不丢失;(2) Function有函数库(Library)的概念,可以注册多个命名函数,按需调用FCALL;(3) Function支持版本管理,可以替换和升级;(4) 聚合管理更清晰——FUNCTION LIST查看所有函数,FUNCTION DELETE删除指定库。调用方式从EVAL/EVALSHA变为FCALL/FCALL_RO(只读版,不允许写操作)。面试关键点:Function不是简单替代Lua,而是在Lua执行引擎之上增加了"函数注册+持久化+版本管理"的应用层。底层还是跑Lua,但开发者体验和运维可靠性大幅提升。Spring Data Redis 3.x已支持Function API。