Spring集成Redis面试题库
生成时间:2026-05-23 15:37 | 题目数:10
【面试题】1. Redis AOF重写的原理是什么?重写期间有新写入怎么办?
【标准答案】AOF重写不是读取旧AOF文件再压缩,而是fork子进程直接读取当前内存中的数据,逐条生成写入命令写入新的AOF文件。重写期间主进程照常处理请求,新写入的命令会同时追加到旧AOF文件(保证安全)和AOF重写缓冲区。子进程完成重写后,主进程把重写缓冲区里的增量命令追加到新AOF文件末尾,然后用新文件原子替换旧文件。整个过程对业务零影响,最终新AOF文件包含了重写开始到结束的所有数据。需要注意:重写缓冲区如果积累过多会导致主进程阻塞(追加时需加锁),所以要合理配置auto-aof-rewrite-percentage和auto-aof-rewrite-min-size避免重写过于频繁或缓冲区过大。
【面试题】2. Spring Data Redis中Lettuce和Jedis的区别?生产环境怎么选?
【标准答案】核心区别在连接模型:Jedis是直连模式,每个操作占用一个连接,多线程需要连接池;Lettuce基于Netty,单个连接天然支持多线程并发复用,通过StatefulRedisConnection线程安全共享。性能上Lettuce更优,非阻塞IO减少线程切换开销。功能上Lettuce原生支持哨兵和集群模式的拓扑自动刷新,Jedis需要手动处理。Spring Boot 2.x之后默认就是Lettuce。生产环境推荐Lettuce,配合连接池(lettuce.pool配置)用于执行阻塞命令(如BLPOP)时避免占用共享连接。如果项目有大量阻塞式操作或依赖Jedis特有的API(如事务watch/multi),才考虑Jedis。实际开发中两者API通过RedisTemplate屏蔽,切换成本很低。
【面试题】3. Redis Cluster为什么用16384个槽位而不是65536?
【标准答案】这是个经典设计权衡问题。原因有三:第一,16384个槽位的bitmap只需2KB(16384/8),节点之间Gossip协议交换槽位信息时开销很小;如果用65536槽位,bitmap就到8KB,集群节点多时心跳包会显著膨胀。第二,集群规模通常不会超过1000个节点,16384个槽分配到几百个节点已经很充裕,每个节点平均管理几十个槽。第三,槽位少意味着迁移单位更合理,迁移一个槽涉及的数据量可控;65536槽迁移粒度太细反而增加管理复杂度。源码中作者antirez也明确说过这是权衡后的选择,2KB的心跳包在大多数场景下完全够用。
【面试题】4. 缓存与数据库双写一致性,延迟双删策略的原理和缺陷是什么?
【标准答案】延迟双删流程:先删缓存→更新数据库→延迟一段时间再删缓存。第二次删除的目的是:在更新数据库期间,可能有读请求把旧数据又写回了缓存,延迟后再删掉这个脏数据。延迟时间设置要大于一次读请求的耗时(通常几百毫秒到1秒)。缺陷很明显:延迟时间难以精确估算,设短了还是有脏数据,设长了用户读到旧数据的时间窗口大;而且第二次删除如果失败就彻底不一致了。生产环境更推荐:用Canal监听binlog异步更新缓存(最终一致性有保障),或者用分布式锁保证读写串行化(强一致性但性能差),再不济也用消息队列确保删除可靠重试。延迟双删只能算低成本方案,不是银弹。
【面试题】5. Redisson分布式锁的看门狗机制原理?什么情况下会失效?
【标准答案】Redisson加锁时如果不显式指定leaseTime,默认锁超时30秒,同时启动一个看门狗(Watchdog)后台线程,每10秒(超时时间的1/3)检查锁是否还被持有,如果持有就自动续期到30秒。这样业务没执行完锁就不会过期释放。看门狗失效的场景:第一,加锁时手动指定了leaseTime,Redisson认为你自行管理超时就不再启动看门狗;第二,持有锁的客户端宕机,看门狗线程随之消亡,锁会在30秒后自动释放(这是预期行为,避免死锁);第三,Redisson实例被垃圾回收或线程池耗尽,看门狗无法续期。实战中要注意:业务执行时间必须远小于锁超时时间,看门狗是兜底不是万能的;集群模式下主节点宕机锁信息可能丢失,需要评估是否用RedLock。
【面试题】6. Redis Stream消费者组如何保证消息不丢失?和Kafka有什么异同?
【标准答案】Stream消费者组通过三个机制保证可靠性:第一,XREADGROUP读取消息后消息进入Pending列表(PEL),直到XACK确认才移除,未确认的消息可以被其他消费者XPENDING+XCLAIM重新消费;第二,XADD时每个消息有唯一ID(时间戳+序列号),天然有序不重复;第三,可以设置MAXLEN限制流长度防止内存溢出。和Kafka的异同:相同点是都有消费者组、消息确认、偏移量概念;不同点是Redis Stream数据在内存中(持久化依赖RDB/AOF),Kafka数据在磁盘;Stream没有分区概念(单分区内有序),Kafka多分区并行度高;Stream适合轻量级消息场景,Kafka适合海量数据高吞吐。Spring Integration Redis提供了StreamMessageListenerContainer可以优雅对接Stream消费。
【面试题】7. Spring Cache @Cacheable的condition和unless有什么区别?生产环境怎么用?
【标准答案】condition在方法执行前判断,决定是否走缓存(包括读和写),不满足则直接执行方法且结果也不缓存;unless在方法执行后判断,只能决定是否将结果写入缓存,不影响从缓存读取。典型用法:condition用于过滤不需要缓存的入参(如@Cacheable(condition="#id > 100")只缓存id大于100的);unless用于过滤不需要缓存的返回值(如@Cacheable(unless="#result == null")空值不缓存,或unless="#result.size() == 0"空集合不缓存)。生产环境特别注意:缓存穿透防护用unless="#result == null"时要小心,不缓存空值意味着每次穿透都会打到数据库;正确做法是缓存空值并设置短TTL:unless="#result == null ? false : true"配合TTL短过期,或者直接用@Cacheable缓存空对象。另外condition和unless可以组合使用。
【面试题】8. Redis BigKey怎么发现和处理?对线上有什么影响?
【标准答案】发现方式:redis-cli --bigkeys扫描各类型的最大key;MEMORY USAGE命令查看具体key的内存占用;SCAN命令遍历配合STRLEN/HLEN/LLEN/SCARD判断;RDB分析工具(rdb-tools)离线分析。BigKey的影响:第一,网络阻塞,读取一个10MB的String会阻塞其他命令;第二,阻塞Redis单线程,DEL大key时(尤其是Hash/List/Set)耗时可能到秒级;第三,集群模式下迁移槽位时大key会导致迁移超时;第四,内存不均匀,某个节点内存远高于其他节点。处理方式:String类型压缩(gzip/snappy);Hash类型拆分为小Hash(按field分组,每组500-1000个field);List/Set分片(按ID取模存多个key);ZSet按分数范围拆分;删除大key用UNLINK(异步删除,4.0+)替代DEL。Spring Data Redis中避免用keys *扫描,用SCAN迭代。
【面试题】9. Redis内存碎片率怎么看?怎么清理?
【标准答案】通过INFO memory查看:mem_fragmentation_ratio = used_memory_rsz / used_memory。比值1-1.5正常;大于1.5说明碎片率高,有大量内存浪费;小于1说明Redis使用了SWAP,性能会急剧下降要立即处理。碎片产生原因:频繁修改和删除导致allocator无法复用已释放的内存块;不同大小的对象分配导致内存对齐浪费。清理方式:4.0+版本用activedefrag yes开启自动碎片整理,Redis后台线程自动移动数据压缩内存;重启Redis实例让操作系统回收内存(需要主从切换保证可用性);调整ziplist/listpack阈值让小对象紧凑存储预防碎片。生产环境建议:开启activedefrag,设置active-defrag-threshold-lower 10和active-defrag-threshold-upper 100控制触发条件,避免碎片整理本身影响性能。
【面试题】10. Redis 7.x的Function和传统Lua脚本有什么区别?Spring Data Redis怎么用?
【标准答案】Function是Redis 7.0引入的脚本管理机制,替代EVAL/EVALSHA方式。核心区别:第一,Function是持久化的,通过FUNCTION LOAD注册后存储在RDB/AOF中重启不丢失,Lua脚本每次重启需要重新加载;第二,Function有函数库概念,可以把多个函数组织在一个library中,更新时用FUNCTION LOAD REPLACE覆盖,版本管理更清晰;第三,Function执行入口从EVAL变为FCALL/FCALL_RO(只读),语义更明确;第四,Function支持flag标记(NO_WRITES/ALLOW_OOM等),Redis可以根据标记优化执行策略。Spring Data Redis 3.x已支持Function API:通过RedisScriptingCommands.functionLoad()注册函数,functionCall()执行函数。目前社区迁移还在进行中,新项目推荐Function,老项目Lua脚本仍可正常运行,两者不冲突。面试中答出Function的持久化和库管理优势就是加分项。