Skip to content

Spring集成Redis面试题库 ​

生成时间:2026-05-21 22:33


【面试题】1. Redis的ZSet底层编码有哪几种?什么条件下会从ziplist升级为skiplist? ​

【标准答案】ZSet底层有两种编码:ziplist(Redis 7.0后改为listpack)和skiplist+hashtable。升级条件:当元素数量超过zset-max-listpack-entries(默认128),或者任意一个member的长度超过zset-max-listpack-value(默认64字节),就会从listpack升级为skiplist+hashtable。skiplist负责范围查询(ZRANGE等),hashtable负责O(1)的单元素查找(ZSCORE等),两者配合实现ZSet的高效操作。面试时顺带提一句Redis 7.0用listpack替代了ziplist,解决了级联更新问题,这是加分项。

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

【标准答案】AOF重写不是读取旧AOF文件来压缩,而是直接读取当前内存数据库的状态,生成最精简的写命令。比如对同一个key做了100次SET,重写后只保留最后一次SET。重写流程:主进程fork子进程,子进程对内存做快照生成新AOF;同时主进程继续处理请求,新写入的命令会同时追加到旧AOF的AOF重写缓冲区中;子进程完成后,主进程把重写缓冲区的命令追加到新AOF末尾,然后用新AOF原子替换旧AOF。这样保证了重写期间不丢失任何数据。关键点:fork利用COW,子进程看到的是fork时刻的内存快照;重写缓冲区保证增量数据不丢。

【面试题】3. Spring Data Redis中Lettuce和Jedis的区别?生产环境选哪个? ​

【标准答案】核心区别:Jedis是同步阻塞式客户端,每个操作阻塞等待响应,多线程需用连接池;Lettuce是基于Netty的异步非阻塞客户端,单个连接就能支持并发访问,线程安全。Lettuce支持响应式编程(Reactive)、自动重连、连接复用,默认就是连接池模式(共享连接+专用连接)。Spring Boot 2.x之后默认就是Lettuce。生产环境建议Lettuce:连接开销小、性能更好、支持Pipeline和事务更优雅。但要注意Lettuce的长连接在Redis重启后需要处理重连,建议配置client配置中的自动重连参数。Jedis适合简单场景或需要深度兼容老代码的项目。

【面试题】4. @Cacheable的condition和unless有什么区别?分别适合什么场景? ​

【标准答案】condition决定是否执行缓存逻辑(方法是否走缓存),在方法执行前评估;unless决定是否将结果放入缓存,在方法执行后评估,可以引用返回值#result。典型场景:condition用于"只有特定参数才缓存",比如condition="#id > 1000"只缓存大ID;unless用于"特定结果不缓存",比如unless="#result == null"不缓存空结果防止缓存穿透,或者unless="#result.size() == 0"不缓存空集合。两者可以同时使用,同时生效时先判condition再判unless。面试加分:提到unless="#result == null"结合@Cacheable做空值缓存防穿透。

【面试题】5. 缓存和数据库双写一致性,Cache Aside模式怎么保证?延迟双删是什么? ​

【标准答案】Cache Aside是最经典的模式:读先查缓存,miss再查DB然后回写缓存;写先更新DB再删缓存。为什么是删缓存而不是更新?因为并发写时更新缓存容易出现覆盖,删缓存让下一次读触发回写更安全。但Cache Aside仍有极小概率不一致:读线程缓存miss查DB,写线程更新DB删缓存,读线程回写旧值到缓存。延迟双删就是在更新DB后先删一次缓存,然后延迟一段时间(比如500ms-1s)再删一次,把延迟期间可能被回写的旧值清掉。延迟时间要大于一次读请求的耗时。更可靠的方案是用Canal监听binlog异步删缓存,但引入了组件复杂度。面试时说清楚每种方案的一致性边界,不要说"绝对一致"。

【面试题】6. Redisson分布式锁的看门狗机制原理是什么?如何实现锁续期? ​

【标准答案】Redisson的看门狗(Watchdog)解决的核心问题是:锁的过期时间设多长都不合适——太短业务没执行完锁就释放了,太长宕机后锁长时间不释放。原理:Redisson加锁时如果不显式指定leaseTime,默认30秒过期,同时启动一个后台定时任务(Netty的HashedWheelTimer),每10秒(默认leaseTime/3)检查锁是否还被持有,如果是就对锁续期到30秒。这样就实现了"业务没完就续期,业务完了或宕机就自动过期"。底层用的是Lua脚本保证续期的原子性。关键代码:tryLockInnerAsync用Lua执行加锁,renewExpiration开定时续期。注意:如果手动指定leaseTime就不会启动看门狗,锁到期自动释放。

【面试题】7. Redis Stream相比Pub/Sub和List有什么优势?消费者组怎么保证消息不丢? ​

【标准答案】Pub/Sub是发后即忘,没有持久化和ACK机制,离线消费者消息丢失;List实现队列没有消费组概念,一条消息只能被一个消费者消费,不支持重复消费;Stream是Redis 5.0引入的,类似Kafka的日志模型,支持持久化、消费者组、消息ACK、消息回溯。消费者组保证不丢的核心:XREADGROUP消费消息后,消息进入Pending列表;消费者必须XACK确认,未ACK的消息在Pending中可见;消费者宕机后,其他消费者通过XPENDING+XCLAIM接管Pending消息重新消费。关键参数:XREADGROUP GROUP group consumer BLOCK count,MAXLEN可以限制Stream长度防止内存爆炸。Stream适合需要可靠消息的场景,简单场景用List或Pub/Sub就够了。

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

【标准答案】滑动窗口限流核心思路:用一个ZSet,member是请求唯一ID,score是时间戳;每次请求时:1)清除窗口外的旧数据ZREMRANGEBYSCORE key 0 (now-window);2)统计窗口内数量ZCARD key;3)如果未超限则ZADD加入当前请求。必须用Lua是因为这三步必须原子执行,否则并发时多个线程同时判断未超限都放行,就超了。Lua脚本示例:local now=ARGV[1] local window=ARGV[2] local limit=ARGV[3] redis.call('ZREMRANGEBYSCORE',KEYS[1],0,now-window) local count=redis.call('ZCARD',KEYS[1]) if count<tonumber(limit) then redis.call('ZADD',KEYS[1],now,now..':'..math.random()) redis.call('EXPIRE',KEYS[1],window/1000) return 1 else return 0 end。Spring Boot中用RedisTemplate.execute(RedisScript, keyList, args...)调用。面试加分:提到ZSet的member要保证唯一(时间戳+随机数),以及设置EXPIRE防止冷key长期占用内存。

【面试题】9. Redis Cluster的16384个槽位是怎么分配的?客户端如何知道key在哪个节点? ​

【标准答案】Redis Cluster把整个key空间分为16384个slot,每个节点负责一部分slot。分配规则:slot = CRC16(key) % 16384。客户端请求时先本地计算key的slot,查本地缓存的路由表找到目标节点直连;如果目标节点不持有该slot,会返回MOVED重定向,客户端更新路由表后重新请求。扩缩容时slot迁移过程中,请求到源节点但key已迁走,会返回ASK重定向,客户端临时重定向到目标节点但不更新路由表。面试时要说清MOVED是永久迁移(更新路由),ASK是临时迁移(不更新路由)。另外,hash tag机制可以让相关key落在同一slot:用{}包裹key的部分内容,如user:{1000}:name和user:{1000}:age,CRC16只计算{}内的部分。

【面试题】10. Spring Session + Redis实现Session共享的原理是什么?有什么注意点? ​

【标准答案】Spring Session通过Filter替换Servlet原生的HttpSession,把Session数据序列化到Redis中,所有服务实例都从Redis读写Session,实现共享。核心配置:@EnableRedisHttpSession注解,底层是SessionRepositoryFilter拦截请求,把HttpSession替换为Spring Session的RedisBackedSession。存储结构:Redis中用Hash存储,key是spring:session:sessions:sessionId,包含session属性、创建时间、最后访问时间等。注意点:1)序列化器建议用Jackson而非JDK序列化,更省内存跨语言兼容;2)Session过期通过Redis的TTL+Spring的定时清理双重保证;3)Cookie的domain要一致,否则跨域Session ID传不过去;4)如果是微服务网关架构,网关要透传Cookie;5)Session粒度不要太细,避免频繁写Redis影响性能,可以用@EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)调整过期时间。