Redis缓存穿透、缓存击穿和缓存雪崩

缓存问题一直以来都是系统优化的热点问题,其中涉及到缓存穿透、缓存击穿、缓存雪崩等多个问题。本文将一一阐述它们的概念及解决方法。

在介绍这三大问题之前,我们需要先了解Redis作为一个缓存中间件,在项目中是如何工作的。首先看一下在没有缓存中间件的时候的系统数据访问的架构图:

Redis缓存穿透、缓存击穿和缓存雪崩

流程:

客户端发起一个查询请求的时候,首先去缓存中查询,如果数据在缓存中存在,则直接将缓存中的数据返回给客户端;如果数据在缓存中不存在,则继续查询数据库,如果数据在数据库中存在,则将该数据放入缓存中,并返回给客户端,如果数据在数据库中也不存在,则直接返回null给客户端。

一.缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,请求会穿过缓存层,直接查询数据库。如果有大量这样的恶意请求,数据库就会承受巨大压力。

举个例子,假设我们的应用需要根据用户的 ID 查询用户的相关信息,缓存也是按照用户 ID 进行缓存的,那么如果某个恶意用户通过构造恶意的 ID 发起大量查询请求,这些恶意请求会穿过缓存直接查询数据库,同时数据库中也没有查到该数据,也没法放入缓存。也就是说,每次这个用户请求过来的时候,都要查询一次数据库,对数据库造成的压力就非常大,甚至可能直接挂掉。如下图所示实线部分就是每次请求的处理逻辑。

Redis缓存穿透、缓存击穿和缓存雪崩

缓存穿透解决方案

解决缓存穿透的方法一般有两种,第一种是缓存空对象,第二种是使用布隆过滤器。

1. 缓存空对象

就是当数据库中查不到数据的时候,我缓存一个空对象,然后给这个空对象的缓存设置一个过期时间,这样下次再查询该数据的时候,就可以直接从缓存中拿到,从而达到了减小数据库压力的目的。但这种解决方式有两个缺点:

(1)需要缓存层提供更多的内存空间来缓存这些空对象,当这种空对象很多的时候,就会浪费更多的内存。

(2)会导致缓存层和存储层的数据不一致,即使在缓存空对象时给它设置了一个很短的过期时间,那也会导致这一段时间内的数据不一致问题。

Redis缓存穿透、缓存击穿和缓存雪崩

2.布隆过滤器

这是比较推荐的方法。所谓布隆过滤器,就是一种数据结构,它是由一个长度为m bit的位数组与n个hash函数组成的数据结构,位数组中每个元素的初始值都是0。在初始化布隆过滤器时,会先将所有key进行n次hash运算,这样就可以得到n个位置,然后将这n个位置上的元素改为1。这样,就相当于把所有的key保存到了布隆过滤器中了。

举个例子,比如我们一共有3个key,我们对这3个key分别进行3次hash运算,key1经过三次hash运算后的结果分别为2/6/10,那么就把布隆过滤器中下标为2/6/10的元素值更新为1,然后再分别对key2和key3做同样操作,结果如下图:

Redis缓存穿透、缓存击穿和缓存雪崩

这样,当客户端查询时,也对查询的key做3次hash运算得到3个位置,然后看布隆过滤器中对应位置元素的值是否为1,如果所有对应位置元素的值都为1,就证明key在库中存在,则继续向下查询;如果3个位置中有任意一个位置的值不为1,那么就证明key在库中不存在,直接返回客户端空即可。如下图:


Redis缓存穿透、缓存击穿和缓存雪崩

当客户端查询key4时,key4的3次hash运算中,有一个位置8的值为0,就说明key4在库中不存在,直接返回客户端空即可。

使用布隆过滤器确实可以解决缓存穿透问题,但同时也带来了两个问题:

  1. 存在误判的情况。数据没及时更新到过滤器会过滤掉有效数据

  2. 存在数据更新问题。

看看为什么会存在误判呢?

上面我已经说过,初始化数据时,针对每个key都是通过多次hash算法,计算出一些位置,然后把这些位置上的元素值设置成1。

但我们都知道hash算法是会出现hash冲突的,也就是说不同的key,可能会计算出相同的位置。

如果某个用户key,经过多次hash计算出的位置,其元素值,恰好都被其他的key初始化成了1。此时,就出现了误判,原本这个key在数据库中是不存在的,但布隆过滤器确认为存在。

通常情况下,布隆过滤器的误判率还是比较少的。即使有少部分误判的请求,直接访问了数据库,但如果访问量并不大,对数据库影响也不大。

此外,如果想减少误判率,可以适当增加hash函数,图中用的3次hash,可以增加到5次;可以扩大数组容量,从而减少hash冲突。

对于 布隆过滤器判断不存在的 key ,则是 100% 不存在的,反证法,如果这个 key 存在,那它每次 Hash 后对应的 Hash 值位置肯定是 1,而不会是 0。布隆过滤器判断存在不一定真的存在。

二.缓存击穿

缓存击穿是指在高并发访问下,一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿过缓存直接请求数据库,就像在一个屏障上凿开了一个洞。

举个例子,假设我们有一个商品信息服务,需要根据商品 ID 查询商品详情,为了加速查询,我们将查询结果缓存下来。但是,当某一时刻这个商品 ID 非常热点,因此会有大量的请求访问这个商品 ID,这个 key 的缓存刚好过期,导致这些大量的并发请求穿透缓存,直接查询数据库,给数据库带来巨大压力。

Redis缓存穿透、缓存击穿和缓存雪崩

缓存击穿解决方案

  1. 加锁:在缓存失效时,不是立即去 load db,使用分布式锁或者串行化操作,确保同一时间只有一个请求能查询数据库并更新缓存,比如使用缓存工具的某些带成功操作返回值的操作( Redis 的 SETNX 操作),在操作成功的情况下,再进行 load db 的操作,并回设缓存。

  2. 数据预加载:系统上线后,将相关的缓存数据直接加载到缓存系统中,不给key设置过期时间即可,这样就不会出现缓存击穿问题。

三.缓存雪崩

什么是缓存雪崩?

缓存雪崩指的是大量的请求无法在 Redis 缓存系统中处理,请求全部打到数据库,导致数据库压力激增,甚至宕机。

出现该原因主要有两种:

  • 大量热点数据同时过期,导致大量请求需要查询数据库并写到缓存;

  • Redis 故障宕机,缓存系统异常。

举个例子,假设我们的应用有 10000 个商品的信息需要缓存,为了加速查询,我们将查询结果缓存下来。由于 Redis 中的数据会设置过期时间,如果这些商品的信息的过期时间都是在同一时刻失效,那么在这个时间点,所有的请求都会落在数据库上,给数据库带来巨大压力。

Redis缓存穿透、缓存击穿和缓存雪崩

缓存雪崩解决方案

1.数据过期时间分散

缓存数据的过期时间分散开,比如我们可以在原有的过期时间基础上增加一个随机值,比如 1-5 分钟的随机时间,这样每一个缓存的过期时间都是不同的,就可以避免集体失效的情况。

2.服务器高可用

针对缓存服务器down机的情况,在前期做系统设计时,可以做一些高可用架构。

比如:如果使用了redis,可以使用哨兵模式,或者集群模式,避免出现单节点故障导致整个redis服务不可用的情况。

Redis缓存穿透、缓存击穿和缓存雪崩

使用哨兵模式之后,当某个master服务下线时,自动将该master下的某个slave服务升级为master服务,替代已下线的master服务继续处理请求。

3.服务熔断和限流限流降级

当系统出现异常情况时,对服务进行限流或者降级,保证核心服务可用。

服务熔断就是当从缓存获取数据发现异常,则直接返回错误数据给前端,防止所有流量打到数据库导致宕机。服务熔断和限流属于在发生了缓存雪崩,如何降低雪崩对数据库造成的影响的方案。     

                                    

原文始发于微信公众号(明月予我):Redis缓存穿透、缓存击穿和缓存雪崩

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272845.html

(0)
明月予我的头像明月予我bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!