面试官:Redis缓存如何回收?
在Redis中,当内存使用达到配置的上限(maxmemory)时,系统会根据设定的回收策略来释放内存。不同的回收策略适用于不同的业务场景,合理选择可以有效提升缓存命中率并保障服务稳定性。
常见的回收策略包括:
volatile-lru:从设置了过期时间的键中使用LRU算法淘汰数据。volatile-random:从设置了过期时间的键中随机淘汰。volatile-ttl:优先淘汰生存时间较短(TTL小)的键。allkeys-lru:对所有键使用LRU算法进行淘汰。allkeys-random:从所有键中随机选择进行淘汰。noeviction:不淘汰任何数据,新增数据时直接返回错误。
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键
如果当前没有键满足回收的前提条件(例如没有设置过期时间),那么volatile-lru、volatile-random和volatile-ttl的行为将与noeviction类似——无法腾出空间,写操作会被拒绝。
选择合适的回收策略应基于应用的访问模式。建议在运行时动态调整策略,并通过监控Redis的INFO命令输出,观察缓存命中率与未命中次数,进而优化配置。
一般经验法则如下:
- 使用
allkeys-lru:适用于请求符合幂律分布的场景,即少数热点数据被频繁访问。若不确定该选哪种策略,这是一个稳妥的选择。 - 使用
allkeys-random:适用于数据访问较为均匀或循环遍历的场景,所有键被访问的概率相近。 - 使用
volatile-ttl:当你希望主动控制哪些缓存对象优先过期,可以通过设置不同TTL值实现。
对于同时需要缓存和持久化功能的场景,allkeys-lru 和 volatile-random 是可行方案,但更推荐的做法是部署两个独立的Redis实例,分别处理缓存与持久化,避免相互干扰。
值得注意的是,为键设置过期时间本身也会占用额外内存资源。因此,在内存压力较大的情况下,采用如allkeys-lru这类无需设置TTL的策略更为高效,因为系统可以直接基于访问频率进行淘汰,而无需维护过期元数据。
回收机制的工作原理
理解Redis的内存回收流程至关重要:
- 客户端执行命令,导致新数据写入,增加内存消耗。
- Redis检查当前内存使用是否超过
maxmemory限制。 - 若超出限制,则按照预设的回收策略删除部分键以释放空间。
- 继续处理后续命令。
这一过程意味着Redis的内存使用会在阈值上下波动——不断触达上限,再通过回收回落至安全范围。然而,若某个操作产生大量内存占用(如将大集合运算结果存入新键),可能迅速突破内存限制,造成短暂的性能下降甚至阻塞。
面试官:击穿、穿透、雪崩、预热解决方案?
缓存穿透
缓存穿透指的是查询一个在数据库中也不存在的数据,由于缓存层未命中且未将该结果缓存,每次请求都会穿透到后端存储系统。在高并发场景下,这种重复查询可能导致数据库负载激增,甚至宕机。恶意攻击者可利用此漏洞发起无效key的高频请求,形成攻击。
解决方案:
- 布隆过滤器(Bloom Filter):提前将所有可能存在的数据哈希映射到一个大型位图中。当请求到来时,先通过布隆过滤器判断是否存在,若判定不存在则直接拦截,避免查库。
- 空值缓存:即使查询结果为空,也将其以短时效(如不超过5分钟)写入缓存。这样可以在一定时间内阻止相同无效请求反复冲击数据库。
缓存击穿
缓存击穿特指某个“热点”key在过期瞬间遭遇超高并发访问。此时大量请求因缓存失效而同时回源至数据库,短时间内造成数据库压力剧增,可能引发响应延迟或崩溃。
解决方案:
- 采用互斥锁机制(mutex),在缓存失效时只允许一个线程去加载数据,其他线程等待并重试获取缓存结果。
- 对热点数据不设置固定过期时间,或使用永不过期策略,配合后台定时任务主动更新缓存。
- 错峰过期:在原有过期时间基础上增加随机偏移量(如1-5分钟),防止多个热点key在同一时刻集中失效。
缓存雪崩
缓存雪崩是指大量key在同一时间段内集中失效,导致瞬时所有请求都转向数据库,造成数据库承受巨大压力,甚至出现服务不可用的情况。
解决方案:
- 加互斥锁(mutex key):当发现缓存未命中时,不立即查库,而是尝试通过
SETNX(Set if Not Exists)等原子操作设置一个临时锁标志。成功获取锁的线程负责加载数据并回填缓存,其他线程则等待并重新尝试读取缓存。 - 提前使用互斥锁:可在缓存即将过期前主动触发异步更新,减少高峰期同步加载的压力。
- 分级设置过期时间:避免统一设置相同的TTL,应根据业务特性分散过期时间,降低集体失效风险。
- 构建多级缓存架构(如本地缓存+Redis),增强系统容灾能力。
在value中内置一个超时时间(timeout1),该时间应小于实际的Memcache过期时间(timeout2)。当从缓存中读取数据并发现timeout1已过期时,立即更新timeout1的值,并将其重新写回缓存。随后,从数据库中加载最新数据,并同步更新至缓存中。
“永远不过期”策略包含两个层面的含义:
- 从Redis的层面来看,key本身未设置过期时间,因此不会因TTL到期而被自动删除,从而避免了热点key突然失效的问题,这被称为“物理”不过期。
- 从功能实现角度出发,虽然key永不过期,但为了避免数据长期静态不变,可在value中单独存储一个逻辑过期时间。当系统访问该key时,判断其内部的逻辑过期时间是否已到,若即将或已经过期,则通过后台异步线程触发缓存重建流程,实现“逻辑”过期机制。
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键
从实际应用效果来看,该方案对系统性能影响较小,具备较高的可用性。唯一的缺点是在缓存重建期间,其他非构建线程可能仍会读取到旧的数据版本。但对于大多数互联网业务场景而言,这种短暂的数据不一致是可以接受的。
总结:
- 缓存穿透:指请求的key在缓存和数据库中均不存在,通常伴随高并发场景,涉及少量特定key。
- 缓存击穿:某个热点key在缓存中失效,而数据库中存在对应数据,高并发下大量请求直接打到数据库,影响集中于少数key。
- 缓存雪崩:大量key在同一时间段内集中失效,导致数据库面临高并发压力,影响范围广,涉及大量key。
尽管三者语义上有所区别,但均可通过引入限流机制或采用互斥锁的方式进行防护,有效防止数据库因瞬时压力过大而崩溃,保障后端服务的稳定性。


雷达卡


京公网安备 11010802022788号







