继续创造,加快生长!这是我参加「日新方案 10 月更文应战」的第 8 天,点击查看活动详情

导言

继 缓存数据一起性问题 后,运用redis中又引来了常见的三种缓存问题,本篇就常见的处理方案来打开剖析。

本篇脑图速览

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

缓存穿透

缓存穿透 :缓存穿透是指客户端央求的数据在缓存中和数据库中都不存在,这样缓存永远不会收效,这些央求都会打到数据库。

所谓穿透,就是直接透过了redis,直接透到数据库

  • 比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客运用此缝隙进行进犯或许压垮数据库。

常见的处理方案有两种:

  • 缓存空目标

    • 长处:结束简略,保护便利
    • 缺点:
      • 额外的内存消耗
      • 或许构成短期的不一起
  • 布隆过滤

    • 长处:内存占用较少,没有剩余key
    • 缺点:
      • 结束杂乱
      • 存在误判或许

对空值缓存

对空值缓存:假设一个查询回来的数据为空(不论是数据是否不存在),我们仍然把这个空效果(null)进行缓存,设置空效果的过期时间会很短,最长不超越五分钟。之后再访问这个数据将会从缓存中获取,保护了后端数据源;

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿
「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

  • 但是这种办法会存在两个问题: 1、假设空值可以被缓存起来,这就意味着缓存需求更多的空间存储更多的键,因为这傍边或许会有许多的空值的键; 2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一起,这关于需求坚持一起性的业务会有影响

布隆过滤器

选用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的 二进制向量(位图) 和一系列随机映射函数(哈希函数)。

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

布隆过滤器可以用于检索一个元素是否在一个集合中:

  1. 假设布隆过滤器判别存在,则放行【或许会误判】,后续进程跟一般查询redis进程是相同的
  2. 不存在,则直接回来,不走redis

一切或许存在的数据哈希到一个足够大的bitmaps中,必定不存在的数据会被这个bitmaps阻挠掉,然后防止了对底层存储系统的查询压力。

  • 它的长处是空间功率和查询时间都远远超越一般的算法
  • 缺点是有必定的误识别率和删去困难。

误判原因在于:布隆过滤器走的是哈希思维,只需哈希思维,就或许存在哈希冲突,数据x和数据y的哈希效果相同,假设只需x,判别y是否存在的时分,因为跟x的哈希值相同,导致布隆过滤器误以为y也存在

布隆过滤器的数据结构

布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个符号,这样下次查询数据是否在数据库时,只需求查询布隆过滤器,假设查询到数据没有被符号,阐明不在数据库中。

布隆过滤器会通过 3 个操作结束符号:

  • 第一步,运用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值;
  • 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应方位。
  • 第三步,将每个哈希值在位图数组的对应方位的值设置为 1;

举个例子,假定有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿
在数据库写入数据 x 后,把数据 x 符号在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假定取模的效果为 1、4、6,然后把位图数组的第 1、4、6 方位的值设置为 1。当运用要查询数据 x 是否数据库时,通过布隆过滤器只需查到位图数组的第 1、4、6 方位的值是否全为 1,只需有一个为 0,就认为数据 x 不在数据库中

留心:此处为什么需求3个hash函数?

若只需1个hash函数,冲突的概率是很大的,都hash到同一个方位,导致误判的概率很大 因而运用多个hash函数,hash到多个方位,只需这几个方位都是1,才阐明x存在,误判的概率会下降些

布隆过滤器由所以根据哈希函数结束查找的,高效查找的一起存在哈希冲突的或许性,比如数据 x 和数据 y 或许都落在第 1、4、6 方位,而事实上,或许数据库中并不存在数据 y,存在误判的状况。

所以,查询布隆过滤器说数据存在,并不必定证明数据库中存在这个数据,但是查询到数据不存在,数据库中必定就不存在这个数据

本质上是一个很大的位图,存储值的时分,用多个hash函数计算出他要存储的方位,比如x,对应hash后的效果是1,2,4, 把这几个方位符号为1。

查询的时分,对x做屡次hash,只需一切hash后的方位符号位都是1,才或许存在

  • 若有一个为0,则必定不存在。

为什么是或许存在?

因为或许x被删去后,又进来了一个y,y的hash效果跟x一模相同,此时会出现误判。

布隆过滤器为什么不好删去元素?

比如现在要删去x,对x做hash,找到了他的存储方位分别是【1,3,9】,我们假设直接把这三个方位改为0,或许会导致“删去”了其他元素

  • 比如y他的存储方位是【2,3,8】,x跟y共用了3这个方位,把3这个方位改为0,会导致y也被“删去”了,根据上边的原理发现不是全1了

怎样处理呢?

最简略的做法就是加一个计数器,就是说位数组的每个位假设不存在就是0,存在几个元素就存具体的数字,而不只是只是存1

那么这就有一个问题,本来存1,一位就可以满足了,但是假设要存具体的数字,或许需求更多的位数,所以带有计数器的布隆过滤器会占用更大的空间

布隆过滤器的典型运用

  • 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 运用BloomFilter来削减不存在的行或列的磁盘查找。防止价值高昂的磁盘查找会大大进步数据库查询操作的功用。
  • 业务场景中判别用户是否阅览过某视频或文章,比如抖音或头条,当然会导致必定的误判,但不会让用户看到重复的内容。
  • 缓存宕机、缓存击穿场景,一般判别用户是否在缓存中,假设在则直接回来效果,不在则查询db,假设来一波冷数据,会导致缓存许多击穿,构成雪崩效应,这时分可以用布隆过滤器当缓存的索引,只需在布隆过滤器中,才去查询缓存,假设没查询到,则穿透到db。假设不在布隆器中,则直接回来。
  • WEB阻挠器,假设相同央求则阻挠,防止重复被进犯。用户第一次央求,将央求参数放入布隆过滤器中,当第2次央求时,先判别央求参数是否被布隆过滤器射中。可以进步缓存射中率。Squid 网页署理缓存服务器在 cache digests 中就运用了布隆过滤器。Google Chrome阅览器运用了布隆过滤器加快安全阅览服务
  • Venti 文档存储系统也选用布隆过滤器来检测从前存储的数据。
  • SPIN 模型检测器也运用布隆过滤器在大规模验证问题时盯梢可达状况空间。

其他手段

设置可访问的名单(白名单)

运用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,假设访问id不在bitmaps里面,进行阻挠,不允许访问。

进行实时监控

当发现Redis的射中率初步急速下降,需求排查访问目标和访问的数据,和运维人员协作,可以设置黑名单束缚服务。

小总结

缓存穿透产生的原因是什么?

  • 用户央求的数据在缓存中和数据库中都不存在,不断建议这样的央求,给数据库带来巨大压力

缓存穿透的处理方案有哪些?

  • 缓存null值
  • 布隆过滤
  • 增强id的杂乱度,防止被猜测id规律
  • 做好数据的基础格式校验
  • 加强用户权限校验
  • 做好抢手参数的限流

缓存击穿

缓存击穿问题也叫抢手Key问题,就是一个被高并发访问而且缓存重建业务较杂乱的key遽然失效了,许多的央求访问会在瞬间给数据库带来巨大的冲击。

常见的处理方案有两种:

  • 互斥锁
  • 逻辑过期

逻辑剖析:假定线程1在查询缓存之后,本来应该去查询数据库,然后把这个数据从头加载到缓存的,此时只需线程1走完这个逻辑,其他线程就都能从缓存中加载这些数据了,但是假定在线程1没有走完的时分,后续的线程2,线程3,线程4一起过来访问其时这个办法, 那么这些线程都不能从缓存中查询到数据,那么他们就会同一时间来访问查询缓存,都没查到,接着同一时间去访问数据库,一起的去实行数据库代码,对数据库访问压力过大

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

运用互斥锁

因为锁能结束互斥性。假定线程过来,只能一个人一个人的来访问数据库,然后防止关于数据库访问压力过大,但这也会影响查询的功用,因为此时会让查询的功用从并行变成了串行,我们可以选用 **tryLock办法 + double check **来处理这样的问题。

锁粒度

只需查询缓存没有射中的状况下,才去加锁

假定现在线程1过来访问,他查询缓存没有射中,但是此时他取得到了锁的资源,那么线程1就会一个人去实行更新缓存的逻辑 假定现在线程2过来,线程2在实行进程中,并没有取得到锁,那么线程2就进入休眠,隔一段时间后再**重试【再次调用自己】**直到线程1把锁开释后,线程2取得到锁,然后再来实行逻辑,此时就可以从缓存中拿到数据了。

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

设置key永不过期

我们之所以会出现这个缓存击穿问题,首要原因是在于我们对key设置了过期时间,假定我们不设置过期时间【永不过期】,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们内存了吗,我们可以选用逻辑过期方案。

我们把过期时间设置在 redis的value中

留心:这个过期时间并不会直接作用于redis,而是我们后续通过逻辑去处理。

假定线程1去查询缓存,然后从value中判别出来其时的数据已通过期了,此时线程1去取得互斥锁,那么其他线程会进行阻塞,取得了锁的线程A会敞开一个 线程去进行重构数据的逻辑直到新开的线程结束这个逻辑后,才开释锁, 而线程1直接进行回来,假定现在线程3过来访问,因为线程线程2持有着锁,所以线程3无法取得锁,线程3也直接回来数据,只需比及新开的线程2把重建数据构建完后,其他线程才能走回来正确的数据。

这种方案奇妙在于,异步的构建缓存【新开一个线程】

缺点在于在构建完缓存之前,回来的都是脏数据

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

比照

前者是献身了可用性,保证了一起性 后者是献身了一起性,保证了可用性 CAP

互斥锁方案:因为保证了互斥性,所以数据一起,且结束简略,因为只是只需求加一把锁而已,也没其他的事情需求操心,所以没有额外的内存消耗 缺点在于有锁就有死锁问题的发生,且只能串行实行功用必定受到影响

逻辑过期方案: 线程读取进程中不需求等候,功用好,有一个额外的线程持有锁去进行重构数据,但是在重构数据结束前,其他的线程只能回来旧数据,且结束起来费事

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

提前预热

守时使命,提前从数据库查询出来,存到缓存里面,而不是比及用户高并发访问了,再去查询数据库,设置缓存

  • 之后等过了高峰期之后再删去无用无效缓存

缓存雪崩

缓存雪崩是指在同一时段许多的缓存key一起失效或许Redis服务宕机,导致许多央求抵达数据库,带来巨大压力。

许多key失效

  1. 不同的Key的TTL添加随机值

分散,不会一起过期,一起失效

  1. 给业务添加多级缓存

nginx缓存 + redis缓存 +其他缓存(ehcache等)

Redis宕机

运用Redis集群进步服务的可用性

假设 Redis 缓存的主节点缺点宕机,从节点可以切换成为主节点,继续供应缓存服务,防止了因为 Redis 缺点宕机而导致的缓存雪崩问题。

给缓存业务添加降级限流战略

降级

因为爆炸性的流量冲击,对一些服务进行有战略的扔掉,以此缓解系统压力,保证现在首要业务的正常运转。它首要是针对非正常状况下的应急服务措施:当此时一些业务服务无法实行时,给出一个共同的回来效果

降级办法
  • 推延服务:比如宣布了议论,重要服务,比如在文章中显现正常,但是推延给用户添加积分,只是放到一个缓存中,等服务平稳之后再实行。
  • 在粒度范围内关闭服务(片段降级或服务功用降级):比如关闭相关文章的举荐,直接关闭举荐区
  • 页面异步央求降级:比如产品详情康复有举荐信息/配送至等异步加载的央求,假设这些信息呼应慢或许后端服务有问题,可以进行降级;
  • 页面跳转(页面降级):比如可以有相关文章举荐,但是更多的页面则直接跳转到某一个地址
  • 写降级:比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证终究一起性即可,此时可以将DB降级为Cache。
  • 读降级:比如多级缓存模式,假设后端服务有问题,可以降级为只读缓存,这种办法适用于对读一起性要求不高的场景

限流

限流之前另一篇有讲过,具体可参看 Redis是怎样结束限流的

熔断

在固守时间窗口内,接口调用超时比率抵达一个阈值,会敞开熔断。

【当某服务出现不可用或呼应超时的状况时,为了防止整个系统出现雪崩,暂时中止对该服务的调用。】

当通过了规守时间之后,服务将从熔断状况康复过来,再次承受调用方的远程调用。

关于降级和限流,后续我们再独自开专栏来具体谈谈

总结

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

「Redis缓存」Redis 三种缓存问题 -- 缓存穿透、缓存雪崩、缓存击穿

收藏 == 白嫖,点赞+关注才是真爱!!!本篇文章如有不对之处,还请在议论区指出,欢迎添加我的微信一起沟通:Melo__Jun

友链

  • MySQL高档篇专栏

  • SSO单点登录专栏

  • Redis入门与实战

  • 我的一年后台操练生计

  • 聊聊Java

  • 分布式开发实战

  • 数据结构与算法