返回 导航

大数据

hangge.com

Redis - 缓存穿透、缓存雪崩、缓存击穿问题解决方案详解

作者:hangge | 2024-12-30 08:47

一、缓存穿透

1,什么是缓存穿透?

(1)缓存穿透是指一些恶意的请求故意查询不存在的 key 值,这样就会导致每次对这个值的查询请求都要到数据库去查询,进而给数据库带来压力。
(2)简单来说,就是指数据既不在缓存中,也不在数据库中。

2,解决办法

(1)非法请求的限制:在请求到达缓存层之前,可以使用一些机制(例如接口层的参数验证、限流等)来过滤掉明显无效的请求,避免进一步访问缓存和数据库。

(2)缓存空值或者默认值:如果查询数据库为空,我们可以在缓存中存储对应的缓存键,但是其对应的值为空对象(通常是一个特殊的值,表示缓存中没有对应数据)。这样,当恶意请求查询不存在的数据时,会命中缓存,而不会直接访问后端存储。这可以防止对后端存储的不必要访问。
这个方案也是有缺点的:如果攻击者每次都用不同的且都不存在的 key 来请求数据,那么这种措施毫无效果。并且,因为要回写特殊值,那么这些不存在的 key 都会有特殊值,浪费了 Redis 的内存。这可能会进一步引起另外一个问题,就是 Redis 在内存不足,执行淘汰的时候,把其他有用的数据淘汰掉。

(3)使用布隆过滤器Bloom Filter)来快速判断请求查询的数据是否存在,避免通过查询数据库来判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,如果布隆过滤器判断数据不存在,可以直接拒绝查询,避免对后端存储的无效查询。
  • 这种方案还有个变种,就是先查询缓存,当缓存没有数据的时候,再去查询布隆过滤器。如果布隆过滤器说有数据,再去查询数据库。
这两种模式没有太大的差别:
  • 先查询布隆过滤器,保护效果会更好,也就是提前挡住了非法请求。
  • 先查询缓存,对正常请求更加友好,因为正常请求大概率命中缓存,直接返回数据,也就不用查询布隆过滤器了。

3,布隆过滤器介绍

(1)布隆过滤器由“初始值都为 0 的位图数组”和“N 个哈希函数”两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
注意:查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据。但是查询到数据不存在,数据库中一定就不存在这个数据。

(2)布隆过滤器会通过 3 个操作完成标记:
  • 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
  • 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置。
  • 第三步,将每个哈希值在位图数组的对应位置的值设置为 1

(3)举个例子,假设有一个位图数组长度为 8,哈希函数为 3 个的布隆过滤器。
  • 在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 146,然后把位图数组的第 146 位置的值设置为 1
  • 当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 146 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
  • 布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 146 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。

二、缓存雪崩

1,什么是缓存雪崩?

    当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,请求都直接访问数据库。如果查询量巨大,会引起数据库压力过大甚至宕机崩溃。

2,解决办法

(1)缓存雪奔一般是由于大量数据同时过期造成的,对于这个原因,有如下几种解决办法:
  • 不同的 key,设置不同的过期时间(例如给这些数据的过期时间加上一个随机数),让缓存失效的时间点尽量均匀
  • (1)偏移量要跟过期时间成正比,不能过低或者过高。比如
    • 如果过期时间是 15 分钟,那么随机偏移量在 0~180 秒都可以。如果数据量不多,那么 0~60 秒也可以。
    • 如果过期时间很长,比如说 4 个小时,也可以把偏移量控制在 0~10 分钟。
    • 如果过期时间很短,比如说只有 10s,这个时候偏移量就只能在 0~3 秒内了。
    (2)当然,偏移量这个东西,除了随机生成,也可以有别的算法。比如说第一条数据加上 1 秒偏移量,第二条数据加上 2 秒,以此类推。
  • 做二级缓存,A1 为原始缓存,A2 为拷贝缓存,A1 失效时,可以访问 A2A1 缓存失效时间设置为短期,A2 设置为长期。
  • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
  • 注意:实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。

(2)Redis 故障宕机也可能引起缓存雪奔,针对这种情况有如下几种解决办法:
  • 服务熔断或请求限流机制
    • 服务熔断机制是暂停业务应用对缓存服务的访问,直接返回错误,不用再继续访问数据库,从而降低对数据库的访问压力,保证数据库系统的正常运行。然后等到 Redis 恢复正常后,再允许业务应用访问缓存服务。该方案全部业务都无法正常工作。
    • 请求限流机制是只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。该方案减少对业务的影响
  • 构建 Redis 缓存高可用集群:如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。

三、缓存击穿

1,什么是缓存击穿?

    缓存击穿是指某个热门的、频繁被访问的 key 在过期的时候,恰好有大量请求访问该数据,导致这些请求绕过缓存直接访问数据库或其他后端存储,增加了数据库负载,降低了系统性能
提示:可以发现缓存击穿跟缓存雪崩很相似,我们可以认为缓存击穿是缓存雪崩的一个子集。

2,解决办法

(1)设置热点数据永不过期:即不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;
(2)使用互斥锁Mutex): 在缓存失效时,通过引入互斥锁,只允许一个线程去查询数据库,其他线程等待查询结果。如果数据库查询结果更新了缓存,那么其他线程就可以直接从缓存获取数据,从而避免同时大量请求直接访问数据库。

附:缓存穿透、缓存雪崩、缓存击穿对比

1,原因对比


2,解决方案对比

评论

全部评论(0)

回到顶部