Skip to content

Spring集成Redis面试题库 ​

生成时间:2026-05-24 19:43 | 题目数:10


【面试题】1. Redis中ZSet底层的两种编码分别是什么?什么条件下会从紧凑编码跳转为跳表编码? ​

【标准答案】ZSet底层有两种编码:listpack(Redis 7.0之前是ziplist)和skiplist+hashtable。当元素数量超过zset-max-listpack-entries(默认128)或者任意一个member的长度超过zset-max-listpack-value(默认64字节)时,编码从listpack跳转为skiplist。listpack是紧凑的连续内存布局,省内存但不适合范围查询;skiplist支持O(logN)的范围操作,hashtable则提供O(1)的member查分值能力,两者组合才能同时满足ZRANGEBYSCORE和ZSCORE两种典型操作的性能需求。面试中要强调这个跳转是单向的,一旦升级就不会降级回来。

【面试题】2. AOF重写机制的原理是什么?重写期间有新的写命令进来怎么处理? ​

【标准答案】AOF重写不是读取旧AOF文件,而是fork子进程直接遍历当前数据库状态,用一条命令代替之前对同一个key的多条历史操作——比如对counter做了100次INCR,重写后只写一条SET counter 100。重写期间主线程正常接收写命令,这些新命令会同时追加到旧AOF缓冲区和"重写缓冲区"(aof_rewrite_buf)。子进程完成重写后,主线程把重写缓冲区的增量命令追加到新AOF文件末尾,然后用新文件原子替换旧文件。这样就保证了重写期间的数据不丢失。面试重点:fork利用操作系统写时复制(COW),子进程看到的是fork那一刻的内存快照,重写缓冲区是保证一致性的关键。

【面试题】3. 缓存穿透、缓存击穿、缓存雪崩分别是什么场景?各自的核心解决方案是什么? ​

【标准答案】穿透是查询的数据在数据库中根本不存在,请求直接穿透缓存打到DB。核心方案:布隆过滤器前置拦截(适合固定不变的数据集)+ 空值缓存设短TTL(适合动态数据)。击穿是热点key过期的瞬间,大量并发请求同时打到DB。核心方案:互斥锁(SET NX抢锁,只放一个线程回源)或逻辑过期(缓存永不过期但存逻辑过期时间,过期后异步更新,其他线程返回旧数据)。雪崩是大面积key同时过期或Redis宕机,瞬间DB压力暴增。核心方案:TTL加随机偏移量避免同时失效 + 多级缓存(本地缓存+Redis)+ 熔断降级保护DB。面试要区分清楚:穿透是数据不存在,击穿是热点key失效,雪崩是大面积失效。

【面试题】4. Spring Data Redis中RedisTemplate的序列化策略怎么选?为什么默认的JdkSerializationRedisSerializer不适合生产使用? ​

【标准答案】JdkSerializationRedisSerializer是RedisTemplate默认序列化器,它把对象序列化为Java字节流,问题很明显:存储内容不可读(二进制乱码)、跨语言不兼容、序列化后体积大、存在Java反序列化安全风险。生产推荐方案:key用StringRedisSerializer,value用Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer。区别是Jackson2JsonRedisSerializer需要手动指定反序列化类型,GenericJackson2JsonRedisSerializer会在JSON中写入@class字段实现自动类型推断,更方便但JSON体积稍大。如果只需要存取字符串,直接用StringRedisTemplate即可,它预配置了String序列化器,开箱即用。

【面试题】5. @Cacheable注解的condition和unless有什么区别?各在什么时机生效? ​

【标准答案】condition在方法执行之前判断,决定是否走缓存逻辑——如果condition返回false,整个缓存逻辑被跳过,既不查缓存也不写缓存,直接执行方法。unless在方法执行之后判断,决定是否将方法返回值写入缓存——如果unless返回true,则不写入缓存。典型用法:condition="#id > 0"表示只对正数ID走缓存;unless="#result == null"表示空结果不缓存(防穿透用空值缓存时不加unless)。执行顺序:先判condition → 查缓存 → 未命中执行方法 → 判unless → 决定是否写缓存。面试中常考的就是这两个的时序差异。

【面试题】6. Redisson的看门狗机制是什么原理?什么场景下看门狗会失效? ​

【标准答案】Redisson加锁时如果不指定leaseTime,默认锁超时30秒,同时启动一个后台线程(看门狗)每10秒(超时时间的1/3)对锁key执行续期,重置为30秒,只要持有锁的客户端存活且未显式释放,锁就不会过期。看门狗失效的场景:一是手动指定了leaseTime,Redisson认为你自己管超时就不会启动看门狗;二是客户端宕机或网络断开,后台线程无法续期,锁到期自动释放(这其实是正确的保护机制)。面试重点:看门狗解决的是业务执行时间不确定导致的锁超时问题,但如果你明确知道业务耗时上限,直接设leaseTime更简单高效,不需要看门狗持续消耗资源。

【面试题】7. Redis Cluster为什么用16384个槽位而不是65535?槽位迁移过程中客户端请求怎么处理? ​

【标准答案】源码作者antirez本人解释过:16384是因为节点间Gossip协议传播的集群状态消息需要携带每个槽位的位图信息,65535个槽位的位图需要8KB,而16384只需2KB,在Gossip协议频繁交换时2KB更高效;而且集群最大节点数1000左右,16384个槽每个节点平均16个槽完全够用。槽位迁移时用ASK和MOVED两种重定向:如果key所在槽正在迁移且源节点还有数据,返回ASK转向,客户端只在下次请求时临时转向目标节点;如果源节点已经完全迁出该槽,返回MOVED转向,客户端会更新本地槽位映射表永久记住新节点。面试要讲清楚ASK是临时重定向,MOVED是永久更新。

【面试题】8. 用Redis实现滑动窗口限流,Lua脚本怎么写?为什么必须用Lua保证原子性? ​

【标准答案】滑动窗口限流的核心思路:用ZSet存储请求时间戳作为score,member用唯一ID防重复,每次请求时先ZREMRANGEBYSCORE移除窗口外的旧记录,再ZCARD统计窗口内请求数,未超限则ZADD添加当前请求并设过期时间。必须用Lua脚本是因为ZRANGEBYSCORE + ZCARD + ZADD这三步不是原子操作,并发下多个线程同时判定未超限会导致超限——比如限流10次,两个线程同时读到9次都认为没超限,结果写入11次。Lua脚本在Redis中单线程原子执行,中间不会被其他命令打断,保证判断和写入的一致性。Spring Boot集成时用RedisTemplate.execute(RedisScript, keys, args)调用。

【面试题】9. Redis 7.x的Function特性相比传统Lua脚本有什么优势?迁移时需要注意什么? ​

【标准答案】Function是Redis 7.x引入的脚本管理机制,相比EVAL直接执行Lua脚本有几个核心优势:一是Function注册后持久化到AOF/RDB,重启后不需要重新加载脚本,而EVAL的脚本缓存只是内存级的;二是Function支持按库(library)组织,可以把相关函数分组管理,而不像EVAL那样散落各处;三是Function可以通过FUNCTION LIST/DELETE统一管理生命周期。迁移注意点:EVALSHA的SHA1缓存机制在Function中不再需要关注,Function用函数名调用;Function默认不支持Lua全局变量(更安全),如果老脚本用了全局变量要改写为局部变量;另外Function和Lua脚本可以共存,不必一次性全量迁移,可以逐步替换。

【面试题】10. 生产环境Redis出现慢查询怎么排查?你实际会关注INFO的哪些指标? ​

【标准答案】第一步查SLOWLOG GET获取慢命令,看具体是什么命令、执行时长、参数——如果是KEYS *这种O(N)命令直接定位问题。第二步看具体key的类型和大小:DEBUG OBJECT看编码,MEMORY USAGE看内存占用,STRLEN/LLEN/SCARD估算元素数量,定位BigKey。第三步查INFO监控指标:used_memory_rss/used_memory比值判断内存碎片率(>1.5说明碎片严重);instantaneous_ops_per_sec看QPS是否打满;connected_clients看连接数;keysapce_hits/keyspace_misses算缓存命中率;latest_fork_usec看fork耗时(AOF重写/RDB时影响大)。常见根因:BigKey操作阻塞、复杂聚合命令、持久化fork阻塞主线程、内存不足触发swap、客户端频繁短连接。解决方向:拆分BigKey、用SCAN替代KEYS、调整持久化策略、开启lazy-free、Pipeline批量操作减少网络RT。