Spring集成Redis面试题库
生成时间:2026-05-21 23:04
【面试题】1. 存储用户信息,用Hash还是String?底层编码原理是什么?
【标准答案】 生产上推荐用Hash。原因有三:
第一,Hash底层用ziplist(字段数少时)或hashtable(字段多时),ziplist紧凑省内存,适合用户信息这种多字段小值的场景。
第二,Hash可以单字段更新,比如只改头像URL,不用把整个用户对象序列化一遍再写回去,省IO也省带宽。
第三,String存对象通常用JSON序列化,改一个字段也得整体覆盖,内存碎片多,GC压力大。
底层编码:Hash在小数据量(field-value对<512,value<64字节)时用ziplist,否则转hashtable。String存整数用int,短字符串用embstr(<44字节),长字符串用raw(SDS)。
【面试题】2. RDB和AOF怎么选?混合持久化原理是什么?
【标准答案】 生产环境推荐混合持久化(Redis 4.0+默认开启)。
RDB优点是恢复快,文件小,但缺点是可能丢数据,因为bgsave是定期快照。AOF优点是数据安全,最多丢1秒数据,但缺点是文件大,恢复慢。
混合持久化原理:AOF重写时,先把当前状态用RDB格式写到AOF文件头部,后面的增量命令继续用AOF追加。加载时先读RDB恢复快照,再回放AOF增量。
配置方式:aof-use-rdb-preamble yes。
实际配置建议:主从架构中,主节点开AOF(everysec),从节点可以只开RDB减轻压力。
【面试题】3. 缓存穿透怎么解决?布隆过滤器原理是什么?
【标准答案】 缓存穿透是指查询一个根本不存在的数据,缓存和数据库都没有,请求直接穿透到数据库。
解决方案有三种:
第一种,空值缓存。查不到也缓存,但设短TTL(比如5分钟),避免长期占用内存。
第二种,布隆过滤器。把所有可能存在的key先加载到布隆过滤器,请求来时先过布隆,布隆说不存在就一定不存在,直接返回。
第三种,接口层校验。比如ID必须是正整数,负数直接拦截。
布隆过滤器原理:用一个位图和多个哈希函数,把key映射到位图上。查询时算哈希看位图,全1可能存在,有1个0就一定不存在。缺点是有误判率,但不存在的key一定会被拦截。Redis 4.0+可以用RedisBloom模块。
【面试题】4. Redisson分布式锁的看门狗机制是什么?如何实现可重入?
【标准答案】 看门狗是Redisson的自动续期机制。锁默认30秒过期,业务还没执行完怎么办?Redisson会在后台起一个定时任务,每10秒(过期时间的1/3)检查一下锁还持有没,持有就续期到30秒。
关键代码:RLock lock = redisson.getLock("myLock"); lock.lock(); 不用传时间参数就会启用看门狗。如果传了时间lock.lock(10, TimeUnit.SECONDS),看门狗就失效,到点就释放。
可重入实现:用Hash结构存锁,key是锁名,field是持有者标识(线程ID),value是重入次数。加锁时value+1,解锁时-1,减到0删key。整个过程用Lua保证原子性。
【面试题】5. 缓存和数据库双写一致性,延迟双删是什么?
【标准答案】 最经典的是Cache Aside模式:读时先读缓存,没有读库再写缓存;写时先更新数据库,再删缓存。为什么是删而不是更新?因为删更简单,避免并发写时数据错乱。
但有个问题:主从延迟场景下,主库写完删缓存,从库还没同步,读请求走从库读到旧数据又写回缓存。这就需要延迟双删:先删缓存,更新数据库,延迟几百毫秒(大于主从延迟)再删一次缓存。
更高级方案:用Canal监听MySQL binlog,异步更新缓存,完全解耦业务代码。
【面试题】6. Redis Cluster为什么用16384个槽?节点间怎么通信?
【标准答案】 16384个槽是空间和成本的平衡。槽太少节点多了分不均,太多心跳包大。每个节点只需要维护自己负责的槽位bitmap,2KB就够了。
槽位分配:CRC16(key) % 16384 计算key属于哪个槽,每个节点负责一部分槽。客户端可以缓存槽位映射,直接路由到正确节点。
节点通信:Gossip协议。每个节点定期(每秒)随机选几个节点交换信息,包括槽位分布、故障检测、新节点加入。好处是去中心化,坏处是有延迟,但秒级传播在大多数场景够用。
故障转移:节点间互相监控,多数节点认为某节点下线(客观下线)后,从节点发起选举,超过半数主节点同意就升级为主节点。
【面试题】7. Redis内存淘汰策略怎么选?LRU和LFU有什么区别?
【标准答案】 先说淘汰策略选型:
volatile-lru:只淘汰设置了过期时间的key,适合缓存场景。allkeys-lru:淘汰所有key中最近最少用的,适合纯缓存无业务数据。volatile-lfu:淘汰访问频率最低的,比LRU更抗突发流量。allkeys-lfu:所有key中淘汰访问频率最低的。volatile-ttl:淘汰快过期的。noeviction:不淘汰,内存满了写入报错,适合有业务数据的场景。
LRU是最近最少使用,LFU是最不经常使用。区别在于:LRU看重最后一次访问时间,LFU看重访问频率。突发大量访问后不再访问的key,LRU会认为它热点,LFU会根据频率衰减慢慢淘汰它。
Redis的LRU/LFU都是近似算法,不是真正维护链表,而是采样一小批(默认5个)淘汰最差的,速度快。
【面试题】8. 缓存雪崩和缓存击穿有什么区别?怎么解决?
【标准答案】 缓存雪崩是大量key同时过期或Redis宕机,请求全打到数据库。解决方案:过期时间加随机数(比如基础时间+随机1-5分钟),多级缓存(本地缓存+Redis),熔断降级(Hystrix/Sentinel),Redis高可用(主从+哨兵)。
缓存击穿是某个超级热点key过期瞬间,大量并发请求穿透到数据库。解决方案:互斥锁(只让一个请求去查库,其他等待),逻辑过期(不设TTL,后台异步更新),热点数据永不过期(配合主动更新)。
区别:雪崩是范围性的,击穿是单key热点。雪崩伤整体,击穿伤单点但压力大。
【面试题】9. Spring Cache的@Cacheable怎么用?condition和unless有什么区别?
【标准答案】 @Cacheable(value = "users", key = "#id"):查库前先查缓存,有就返回,没有执行方法再写缓存。
@CachePut:每次都执行方法,结果写入缓存,适合更新场景。
@CacheEvict:删除缓存,allEntries=true清空整个缓存区。
condition和unless区别:condition在方法执行前判断,决定是否走缓存;unless在方法执行后判断,决定是否写入缓存。
例子:@Cacheable(value = "users", condition = "#id > 0", unless = "#result == null"),ID必须大于0才走缓存,结果为null不写缓存(避免缓存穿透)。
TTL配置:在CacheManager里配,或者用@Cacheable(value = "users#60s")语法(需要自定义CacheResolver支持)。
【面试题】10. Redis限流怎么实现?滑动窗口和令牌桶有什么区别?
【标准答案】 固定窗口:用INCR计数,EXPIRE设过期时间。问题:窗口边界可能突发双倍流量。
滑动窗口:用ZSet存请求时间戳,每请求一次加一个时间戳,用ZREMRANGEBYSCORE删窗口外的,ZCARD计数。精度高但内存大。
令牌桶:固定速率往桶里放令牌,请求来了拿令牌,拿不到就拒绝。适合允许一定突发流量。用Lua脚本原子实现:桶容量、当前令牌数、上次放令牌时间都存Redis,请求来时算应该补充多少令牌,再判断够不够扣。
漏桶:请求排队,固定速率处理,适合保护下游,不允许突发。
Spring Boot集成:用Redisson的RRateLimiter,或自己写Lua脚本+RedisTemplate.execute()。
生产推荐:令牌桶应对正常业务,滑动窗口严格控频防刷接口。
题目统计:共10道题,覆盖数据结构、持久化、缓存实战、分布式锁、集群架构、内存管理、限流算法等核心考点。