Redis——缓存穿透 / 雪崩 / 击穿

  • 缓存穿透
    • BloomFilter
  • 缓存雪崩
  • 缓存击穿
  • 布隆过滤器:某样东西一定不存在或者可能存在

1. 缓存穿透 / 雪崩 / 击穿

从事态严重性来讲:穿透 > 雪崩 > 击穿

  • 缓存穿透:数据库中数据不存在,缓存也不存在。每次都大量请求数据库,打穿数据库
    • 解决方法
      • 1. 缓存预热:缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,避免用户请求的时候,再去加载相关的数据
      • 2. 布隆过滤器:见下
  • 缓存雪崩:原有缓存失效(过期),新缓存未到期间;或者redis挂了。大量请求打向数据库
    • 解决方法
      • 1. 关键点就在于让key错开时间失效:设置key过期时间的时候加上一个随机数
      • 2. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新缓存
        • 缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存
        • 缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存
  • 缓存击穿:缓存中某个热点key扛着大量的并发请求,一旦它过期,失效瞬间,大量请求打向数据库
    • 解决方法
      • 1. 延长过期时间,或者永不过期(可以选择与缓存雪崩,解决方法2,添加缓存标记,使得看上去永不过期)
  • 宕机:会引起缓存雪崩
    • 解决方法:集群搭建保证高可用

2. 布隆过滤器 BloomFilter

作用:一种巧妙的概率型数据结构,告诉你某样东西一定不存在或者可能存在

  • 思路:加在redis的上游,访问redis前先用布隆过滤器,过滤大部分不存在key的请求访问
  • 原理:设置三个hash函数{x,y,z},设置一个长度为18的bit array[] 用于hash映射,假设有两个key:str1, str2,
    • str1经过x,y,z三个hash函数映射到了2,13,16;str2经过x,y,z三个hash函数映射到了2,5,11
    • 每个key访问布隆的时候先看看自己映射的三个index是否都为1
    • eg. str1访问布隆,映射到[2,13,16],发现都为1,说明存在——>放行,访问redis
    • eg. 假设str3访问布隆,映射到[2,3,4],发现3,4为0,说明不存在——>拦截,减少对db压力
  • 可能会误判:
    • 传统模式下:不会对布隆过滤器作key删除操作(例如上图中的str1和str2都被映射到了index=2,如果删除str1,则str2也不能访问了)
      • 但是名为 Counting Bloom filter 的变种可以用来测试元素计数个数是否绝对小于某个阈值,它支持元素删除
    • 工程上为了降低冲突,会设置inverse bloom-filter空间比较大,并多次hash防干扰
  • 实际使用:guava提供的类库
  • 自己实现:
    • import java.util.Arrays;
      import java.util.BitSet;
      import java.util.concurrent.atomic.AtomicBoolean;
      
      public class MyBloomFilter {
          //你的布隆过滤器容量
          private static final int DEFAULT_SIZE = 2 << 28;
          //bit数组,用来存放结果
          private static BitSet bitSet = new BitSet(DEFAULT_SIZE);
          //后面hash函数会用到,用来生成不同的hash值,可随意设置,别问我为什么这么多8,图个吉利
          private static final int[] hashInts = {1, 6, 16, 38, 58, 68};
      
          //add方法,计算出key的hash值,并将对应下标置为true
          public void add(Object key) {
              Arrays.stream(hashInts ).forEach(i -> bitSet.set(hash(key, i)));
          }
      
          //判断key是否存在,true不一定说明key存在,但是false一定说明不存在
          public boolean isContain(Object key) {
               boolean result = true;
              for (int i : hashInts) {
              	//短路与,只要有一个bit位为false,则返回false
                  result = result && bitSet.get(hash(key, i));
              }
              return result;
          }
      
          //hash函数,借鉴了hashmap的扰动算法,强烈建议大家把这个hash算法看懂,这个设计真的牛皮加闪电
          private int hash(Object key, int i) {
              int h;
              return key == null ? 0 : (i * (DEFAULT_SIZE - 1) & ((h = key.hashCode()) ^ (h >>> 16)));
          }
      }

Ref:

改进:布谷鸟过滤器:https://zhuanlan.zhihu.com/p/68418134

https://blog.csdn.net/qq_33709582/article/details/108407706

https://www.zhihu.com/search?type=content&q=%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4

猜你喜欢

转载自blog.csdn.net/qq_41157876/article/details/114024560