暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

缓存雪崩, 缓存穿透, 缓存击穿

logerJava 2021-08-20
236


前言


Redis 作为业界经典缓存, 无论是从使用还是面试来讲都存在知识点, 关于缓存雪崩, 缓存穿透, 缓存击穿, 更是 "八股文" 中的 "八股文", 不了解这几个高并发场景简历上都不好意思写 Redis, 虽然我们经常在面试题中见到这些 Redis 高并发场景问题, 但可能对实际情况并不了解, 我们今天就来盘一盘这几个场景


缓存雪崩


首先我们来定义一下缓存雪崩的概念 : Redis 中大量 key 同一时间失效, 导致大量请求落库

怎么去理解呢 ? loger 来举个例子可能不太形象, 但是大家一下就会明白 :

最常见的就属于秒杀场景了, 比如有一个秒杀活动, 秒杀存在 24 小时, 从第一天的 12 点开始一直到第二天的 12 点结束, 这时从首页跳转到商品的 key 不用说肯定会放到 Redis 中, 因为访问量比较大, 此时当在结束时间, 也就是第二天的 12 点涌入大量活动用户, 会发生什么呢 ? 本来可以抗住每秒大量请求的 Redis 中 key 全部失效, 因为活动结束了, 那么这时所有都请求将会全部落入数据库, 数据库必然是扛不住的, 这也就是缓存雪崩

这种场景一般在生产环境出现都是灾难性的, 因为数据库崩溃后, 无论是哪个服务的数据库宕机, 所有依赖这个库的接口都会报错, 所以如果你没有做熔断的话基本上就是连锁反应, 一片全挂, 就算你重启, 用户依旧在访问, 怎么重启都会挂, 这个时候你总不能等用户失去耐心不去访问了, 然后再去重启吧

那么我们怎么去处理这种情况呢 ?

我们将缓存雪崩分为第三个阶段, 分别来看一下怎么办 :

预防方案, 避免雪崩的发生 :

说起来也简单, 产生雪崩的原因是同一时间大量的 key 失效, 那么只要让他们不同时间失效不就行了 ? 在向 Redis 存数据的时候, 将每个 key 都设置上不同的过期时间, 加上随机值之类的, 这样就可以确保不会有大量 key 同时失效

同理我们也可以设置热点数据永不过期

如果是集群部署的 Redis, 那么也可以将热点数据均匀分布在不同的 Redis 库中, 这样也可以避免全部失效问题

万一发生了怎么办 ?

如果万一发生了, 那么处理就只有一个原则, 不要打崩数据库

我们可以设置本地缓存 (ehcache) + 熔断降级 (hystrix) + 限流等一系列操作, 尽量避免请求过多的直接打入数据库

如果做了上面这些, 一不小心还是挂掉了怎么去处理呢 ?

这个时候就要通过 Redis 的持久化措施, AOF + RDB 重启后快速回复缓存数据

这里留一个问题, Redis 集群下将热点数据均匀分布在不同的 Redis 库中, 那么我获取数据的时候怎么从不同的 Redis 中获取呢 ? 这是 loger 面试腾讯的时候的真题哦, 结尾会给出答案 !


缓存穿透


概念 : 缓存和数据库中都没有的数据, 而用户不断发起请求, 导致数据库压力过大, 严重会击垮数据库

举一个例子 :

程序缓存与数据库一般按照 Cache Aside Pattern 设计, 也就是说先访问缓存, 缓存没有再去找数据库, 比如用户通过接口查询用户名为 "loger" 的相关信息, 而不论是缓存中, 还是数据库中 "loger" 是不存在的, 这种情况一般来说没有什么问题, 但是如果被恶意攻击者利用, 发起 DDoS 攻击等就会引起严重问题, 导致数据库崩溃等一系列影响

像是小一些的系统, 比如你本地启动的服务, 或者小型阿里云服务器, 用 PostMan 不断发起请求就可以直接让你挂掉

解决缓存穿透主要从两个方面考虑 :

  • 过滤方式

  • 临时返回

我们可以通过给接口增加校验, 比如鉴权校验, 参数校验等, 如果存在不合法的参数直接做短路返回操作, 同理我们也可以使用布隆过滤器去处理

另外一种方式就是做短时间的临时返回, 将不存在的 key 对应的 value 设置为 null, 或者进行信息提示, 然后返回, 这个 null 值的有效时间可以设置短一些, 因为如果设置的时间太长会导致正常情况下也没法使用

通过以上两种方式基本就可以防止穿透问题, 但是我们要考虑的是作为一个正常用户, 当发现访问对象没有的时候, 是不会进行频繁访问的, 可能刷个几次就退出了, 只有攻击者才会在单秒中发起洪水攻击, 所以我们可以通过 Nginx 的配置, 让运维同学查看某些 IP 的异常行为来拉黑 IP


缓存击穿


概念 : 一个非常热点的Key,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库

举个例子, 比如微博的热搜, 当然热搜是不可能 key 失效的, 这里是举一个例子, 当某一个热搜正处在热点时, 它突然缓存失效了, 这个时候所有访问热搜的请求都会打到数据库, 从而使数据库压力激增

怎么解决呢 ?

老办法, 设置热点数据永不过期, 或者加上互斥锁去解决这个问题

互斥锁的解决流程 :

  • 首先在缓存中获取数据

  • 没有的话就获取锁

  • 查询数据库的数据, 然后搞到缓存里面

  • 然后释放锁

  • 返回

这样就能避免一瞬间多个请求都打到数据库, 当然这种分布式锁的操作, 要考虑的东西很多, 比如锁的过期时间啊, setnx 和 设置过期时间的原子性, lua 脚本啥的, 但是这就不是本篇范畴了, 下面的分布式锁篇会讲


结尾


不知道大家想明白分布式集群怎么从不同的 Redis 获取数据, 没想明白的话 loger 给一个提示, 想一下 HashMap 是怎么做的, Redis 像不像一个 HashMap 呢 ? 不同的 Redis 节点就好像 Hash 桶, 我们可以通过 Hash 取模的办法均匀分配到不同的 Redis 节点中, 然后再获取, 再深入一点的话大家可以看一下一致性Hash 算法

我是 loger, 关注我看更多知识分享, 点赞啦老铁 👍





文章转载自logerJava,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论