Reids之缓存雪崩、缓存穿透

与世无争的帅哥 提交于 2020-02-10 14:12:29

1、缓存雪崩

缓存雪崩指的是原有的缓存数据出现了大批量的缓存过期,造成一时间大批量并发请求都到了数据库,造成数据库的压力激增,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

【解决方案】
从上面的分析可以看出只有当缓存同时大批量过期的时候,才会出现缓存雪崩的情形,所以只要想办法让缓存过期的时间分散开来即可。那方法就多了,比如说将热数据的缓存时间设置长一点,冷数据的过期时间设置得短一点,另外数据也可以分类设置不同的过期时间,或者加一个随机扰乱因子。

2、缓存穿透

缓存穿透是指查询一个不存在于缓存且不存在于数据库的数据,这时如果遭到恶意攻击,频繁地请求这个缺失数据,必然会每次都要读缓存和读数据库,增加了数据库的压力负担。

【解决方案】

  1. 对于缺失的数据,每次请求完数据库之后,都要更新到缓存,设置其key对应的value为空即可。这样当下一次请求该缺失数据对应的key,就可以直接通过缓存判断了,不需要再去请求数据库。
    ——缺点:显然,当数据缺失很多的时候,必然会极大地浪费缓存的内存空间。
  2. 采取bloom filter(布隆过滤器),布隆过滤器的原理如下。

3、布隆过滤器

首先明确我们要解决的问题,即如何快速判断一个数据是否存在于数据库中。那我们自然而然想到的便是采取hash思想,只需要将数据库中的所有数据key存储到一个hash结构中,便可以快速判断。但是这里有一个问题在于,一般我们面临的实际数据会很大,少则上亿的数据量,而且每一个数据key所占字节数都有可能会很大。我们假设数据量为109,每一个key的平均字节数为8byte,则存储这一部分key所需要的内存为 8*109byte ≈ 8GB,显然对于redis这种内存数据库而言,这样大的内存消耗是不可能承受的。所以我们提出了一个问题,是否存在这样一个数据结构,即能够快速查找,又能够尽可能的降低内存占用。

3.1 BitMap

承接上面的问题,BitMap就是这样一个数据结构,即能够快速查找,又能够尽可能的降低内存占用。其原理在于,用一个bit位来表示一个key,无论这个key多大,都可以通过hash散列到具体某一个bit位上。这样每次查找该key时,都先通过一个hash函数进行映射,然后找到BitMap上对应bit位是0还是1,来判断其key是否存在。下面我们用一个具体的实例来说明。

场景一:20亿个整数中找出不重复的整数的个数,内存不足以容纳这20亿个整数。 (大数据查重)

  • 首先,根据“内存空间不足以容纳这05亿个整数”我们可以快速的联想到Bit-map。下边关键的问题就是怎么设计我们的Bit-map来表示这20亿个数字的状态了。其实这个问题很简单,一个数字的状态只有三种,分别为不存在,只有一个,有重复。因此,我们只需要2bits就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为00,存在一次01,存在两次及其以上为11。那我们大概需要存储空间2G左右。
  • 接下来的任务就是把这20亿个数字放进去(存储),如果对应的状态位为00,则将其变为01,表示存在一次;如果对应的状态位为01,则将其变为11,表示已经有一个了,即出现多次;如果为11,则对应的状态位保持不变,仍表示出现多次。
  • 最后,统计状态位为01的个数,就得到了不重复的数字个数,时间复杂度为O(n)。

场景二:对10亿个不重复的整数进行排序。(大数据排序)

  • 比如我要对{1,5,7,2}这四个byte类型的数字做排序,该怎么做呢?我们知道byte是占8个bit位,其实我们可以将数组中的值作为bit位的key,value用”0,1“来标识该key是否出现过?下面看图:
    在这里插入图片描述
    从图中我们精彩的看到,我们的数组值都已经作为byte中的key了,最后我只要遍历对应的bit位是否为1就可以了,那么自然就成有序数组了。

  • 可能有人说,我增加一个13怎么办?很简单,一个字节可以存放8个数,那我只要两个byte就可以解决问题了。
    在这里插入图片描述
    可以看出我将一个线性的数组变成了一个bit位的二维矩阵,最终我们需要的空间仅仅是:3.6G/32=0.1G即可

  • 要注意的是bitmap排序不是N的,而是取决于待排序数组中的最大值,在实际应用上关系也不大,比如我开10个线程去读byte数组,那么复杂度为:O(Max/10)。

虽然BitMap已经尽可能的压缩了空间了,但是对于内存数据库而言,其空间消耗依旧很大,1G的数据key就至少需要1G的空间,所以考虑能否进一步降低空间。

3.2 布隆过滤器

鉴于上述问题,我们发现在只有两种状态时,bitmap要求key和bit位一一对应,而且其空间大小受极值的影响,我们假设Redis的内存空间为2G,则它至多可以存储2G的key。但是如果我们让key和bit形成一对多的关系,且不受极值的影响,那它就可以存储更多的key。举个例子:我们假设用k=3个bit为来表示以个key,则2G的bit可以表示C2G3C_{2G}^3 个数,明显大于2G的空间。而布隆过滤器正是采取这样的思想,通过多个hash函数对一个key进行散列,这样一个key就可以对应多个bit位,从而进一步降低了空间。

所以Bitmap的好处在于空间复杂度不随原始集合内元素的个数增加而增加,而它的坏处也源于这一点——空间复杂度随集合内最大元素增大而线性增大。

所以接下来,我们要引入另一个著名的工业实现——布隆过滤器(Bloom Filter)。如果说Bitmap对于每一个可能的整型值,通过直接寻址的方式进行映射,相当于使用了一个哈希函数,那布隆过滤器就是引入了k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。下图中是k=3时的布隆过滤器。
在这里插入图片描述

但是我们也发现了一个问题,布隆过滤器,可能存在误报的可能,比如不同的key由于散列的bit位存在重叠的可能,所以有可能导致,某个key虽然不在数据库中,但是其对应的bit位却全是1,这样就造成了误报。但是布隆过滤器可以保证,如果某个key所对应的key不全为1,它必然不存在于数据库中。

参考链接:https://blog.csdn.net/zdxiq000/article/details/57626464

布隆过滤器通过引入一定错误率,使得海量数据判重在可以接受的内存代价中得以实现。从上面的公式可以看出,随着集合中的元素不断输入过滤器中(nnn增大),误差将越来越大。但是,当Bitmap的大小mmm(指bit数)足够大时,比如比所有可能出现的不重复元素个数还要大10倍以上时,错误概率是可以接受的。相比于单纯的bitmap,这个算法跳出了空间复杂度对待判元素值域的依赖,转而依赖总元素个数,这是一个更加工程可实现的算法——前者不管你的数集有多大,所需要的内存空间是一定的;后者,数集越大,想要达到相同误判率,所需要的内存空间就越大。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!