场景应用:缓存穿透、缓存击穿、缓存雪崩有什么区别,该如何解决?
文章目录
今日单词:policy:策略
复习:Redis的淘汰与过期策略
Redis的淘汰策略
当写入数据将导致超出maxmemory限制时,Redis会采用maxmemory-policy所指定的策略进行数据淘汰,该策略一共包含如下8种选项:
策略 | 描述 | 版本 |
noeviction | 直接返回错误; | |
volatile-ttl | 从设置了过期时间的键中,选择过期时间最小的键,进行淘汰; | |
volatile-random | 从设置了过期时间的键中,随机选择键,进行淘汰; | |
volatile-lru | 从设置了过期时间的键中,使用LRU算法选择键,进行淘汰; | |
volatile-lfu | 从设置了过期时间的键中,使用LFU算法选择键,进行淘汰; | 4.0 |
allleys-random | 从所有的键中,随机选择键,进行淘汰; | |
allkeys-lru | 从所有的键中,使用LRU算法选择键,进行淘汰; | |
allkeys-lfu | 从所有的键中,使用LFU算法选择键,进行淘汰; | 4.0 |
其中,volatile前缀代表从设置了过期时间的键中淘汰数据,allkeys前缀代表从所有的键中淘汰数据。
关于后缀,ttl代表选择过期时间最小的键,random代表随机选择键,需要我们额外关注的是lru和lfu后缀,它们分别代表采用lru算法和lfu算法来淘汰数据。
LRU(Least Recently Used)是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来!
- 标准LRU:把所有的数据组成一个链表,表头和表尾分别表示MRU(Max Recently Used)和LRU(Least Recently Used)端,即最常使用端和最少使用端。刚被访问的数据会被移动到MRU端,而新增的数据也是刚被访问的数据,也会被移动到MRU端。当链表的空间被占满时,它会删除LRU端的数据。
- 近似LRU:Redis会记录每个数据的最近一次访问的时间戳(LRU)。Redis执行写入操作时,若发现内存超出maxmemory,就会执行一次近似LRU淘汰算法。近似LRU会随机采样N个key,然后淘汰掉最旧的key,若淘汰后内存依然超出限制,则继续采样淘汰。可以通过maxmemory_samples配置项,设置近似LRU每次采样的数据个数,该配置项的默认值为5。
LRU算法的不足之处在于,若一个key很少被访问,只是刚刚偶尔被访问了一次,则它就被认为是热点数据,短时间内不会被淘汰。
LFU算法正式用于解决上述问题,LFU(Least Frequently Used)是Redis4新增的淘汰策略,它根据key的最近访问频率进行淘汰。LFU在LRU的基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用LFU策略淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出内存。如果两个数据的访问次数相同,LFU再比较这两个数据的访问时间,把访问时间更早的数据淘汰出内存。
Redis的过期策略
Redis支持如下两种过期策略:
- 惰性删除:客户端访问一个key的时候,Redis会先检查它的过期时间,如果发现过期就立刻删除这个key。
- 定期删除:Redis会将设置了过期时间的key放到一个独立的字典中,并对该字典进行每秒10次的过期扫描,过期扫描不会遍历字典中所有的key,而是采用了一种简单的贪心策略。该策略的删除逻辑如下:
- 从过期字典中随机选择20个key;
- 删除这20个key中已过期的key;
- 如果已过期key的比例超过25%,则重复步骤1。
那么我们应如何合理的设置过期时间
- 热点数据不设置过期时间,使其达到“物理”上的永不过期,可以避免缓存击穿问题;
- 在设置过期时间时,可以附加一个随机数,避免大量的key同时过期,导致缓存雪崩。
缓存穿透、缓存击穿、缓存雪崩有什么区别,该如何解决?
缓存穿透:
问题描述:
客户端查询根本不存在的数据,使得请求直达存储层,导致其负载过大,甚至宕机。出现这种情况的原因,可能是业务层误将缓存和库中的数据删除了,也可能是有人恶意攻击,专门访问库中不存在的数据。
解决方案:
- 缓存空对象:存储层未命中后,仍然将空值存入缓存层,客户端再次访问数据时,缓存层会直接返回空值。
- 布隆过滤器:将数据存入布隆过滤器,访问缓存之前以过滤器拦截,若请求的数据不存在则直接返回空值。
缓存击穿:
问题描述:
一份热点数据,它的访问量非常大。在其缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。
解决方案:
- 永不过期:热点数据不设置过期时间,所以不会出现上述问题,这是“物理”上的永不过期。或者为每个数据设置逻辑过期时间,当发现该数据逻辑过期时,使用单独的线程重建缓存。
- 加互斥锁:对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待。这个线程访问过后,缓存中的数据将被重建,届时其他线程就可以直接从缓存中取值。
缓存雪崩:
问题描述:
在某一时刻,缓存层无法继续提供服务,导致所有的请求直达存储层,造成数据库宕机。可能是缓存中有大量数据同时过期,也可能是Redis节点发生故障,导致大量请求无法得到处理。
解决方案:
- 避免数据同时过期:设置过期时间时,附加一个随机数,避免大量的key同时过期。
- 启用降级和熔断措施:在发生雪崩时,若应用访问的不是核心数据,则直接返回预定义信息/空值/错误信息。或者在发生雪崩时,对于访问缓存接口的请求,客户端并不会把请求发给Redis,而是直接返回。
- 构建高可用的Redis服务:采用哨兵或集群模式,部署多个Redis实例,个别节点宕机,依然可以保持服务的整体可用。