缓存雪崩、击穿、穿透、布隆过滤器

黄鹏宇 125 2023-06-03

缓存雪崩

image-1685731138642

产生原因

  1. redis缓存的key,在同一时间大量失效,导致大量请求直接打到数据库,造成数据库挂掉
  2. redis宕机

解决办法

  1. 设置小随机失效时间,给这些数据过期时间增加一些较小的随机数,不同数据的过期时间有差别,但是差别又不大
  2. 服务降级

缓存击穿

产生原因

要访问的数据既不是Redis缓存中也不再数据库中,导致请求在访问缓存时,发生缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。
如果应用持续有大量请求访问数据,就会同时给缓存和数据库带来巨大压力。
业务层误操作:缓存中的数据和数据库中的数据都被误删除,所以缓存和数据库中都没有数据;
恶意攻击:专门访问数据库中没有的数据

解决办法

  1. 缓存空值或者缺省值
    一旦发生缓存穿透,针对查询数据在Redis缓存一个空值或者业务层协商确定的缺省值。应用发生后续请求在进行查询时,可以直接从Redis读取空值或者缺省值返回给业务应用,避免大量请求发送给数据库处理,保持数据库的正常运行。
  2. 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
  3. 对请求参数进行校验,避免请求进入缓存或者数据库。

缓存穿透

产生原因

热点key失效,大量请求发送到数据库,导致数据库压力激增影响数据库处理其他请求。

解决办法

  1. 对于访问特别频繁的热点数据,不设置过期时间。
  2. 对于热点数据的请求访问,都在缓存中进行处理。
  3. 数据库锁,并设置延时,重新查询redis

布隆过滤器

https://www.bilibili.com/video/BV11M4y157uK
image-1685732816184

package com.uuorb.xueersi.util;

import org.springframework.stereotype.Component;

import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.BitSet;

@Component
public class BloomUtil {
    private static final BloomUtil _instance;

    /**
     * 位数组大小
     */
    private static final int DEFAULT_SIZE = 2 << 24;

    /**
     * 通过这个数组创建多个Hash函数
     */
    private static final int[] SEEDS = new int[]{6, 18, 64, 89};

    /**
     * 初始化位数组,数组中的元素只能是 0 或者 1
     */
    private BitSet bits = new BitSet(DEFAULT_SIZE);

    /**
     * @return
     */
    public static BloomUtil getInstance() {
        return _instance;
    }

    /**
     * Hash函数数组
     */
    private MyHash[] myHashes = new MyHash[SEEDS.length];

    /**
     * 添加元素到位数组
     */
    public void add(Object value) {
        for (MyHash myHash : myHashes) {
            bits.set(myHash.hash(value), true);
        }
    }

    /**
     * 判断指定元素是否存在于位数组
     */
    public boolean contains(Object value) {
        boolean result = true;
        for (MyHash myHash : myHashes) {
            result = result && bits.get(myHash.hash(value));
        }
        return result;
    }

    public BloomUtil() {
        // 初始化多个不同的 Hash 函数
        for (int i = 0; i < SEEDS.length; i++) {
            myHashes[i] = new MyHash(DEFAULT_SIZE, SEEDS[i]);
        }
    }

    static {
        _instance = new BloomUtil();
    }


    /**
     * 自定义 Hash 函数
     */
    private class MyHash {
        private int cap;
        private int seed;

        MyHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        /**
         * 计算 Hash 值
         */
        final int hash(Object obj) {
            return (obj == null) ? 0 : Math.abs(seed * (cap - 1) & (obj.hashCode() ^ (obj.hashCode() >>> 16)));
        }
    }


}