redis 缓存穿透/击穿/雪崩

220 阅读5分钟

1.缓存穿透

1.1 概念

一些恶意的请求会故意查询不存在的 key,请求量很大,就会对后端系统造成很大的压力。这就叫 做缓存穿透。

1.2 解决方案

  1. 布隆过滤器:对一定不存在的 key 进行过滤。可以把所有的可能存在的 key 放到一个大的 Bitmap 中,查询时通过该 bitmap 过滤。
  2. 缓存null值,针对空的数据,设置过期时间,比如 10 分钟,快速过期,防止太多的空值问题。
  3. 前置过滤:如果缓存的 key 是比如 ID 之类,也可以根据一定的范围规则去提前过滤,比如缓存的 key 明确知道在 1-10 万的范围之后,那么过滤掉在这个范围之外的请求直接返回就可以了。

1.2.1布隆过滤器原理

当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。

36e75faa44d18bc56b8b3e5573c2b006.jpg

1.2.2布隆过滤器优缺点

  1. 存在误判,只能判定元素一定不存在容器中, 无法通过结果判定元素一定在容器中,即bitmap中对应的位都是1,则其不一定存在 ,
  2. 放入容器中的数据无法删除

1.2.3布隆过滤器的实现

使用bloom filter时,重点是 预估数据量n 以及 期望误判率fpp

实现bloom filter时,重点是 hash函数选取 以及 bit数组的大小

48d60669b8d228832a7198eebf17815b.png

参考文献: 布隆过滤器(BloomFilter) (qq.com)

1.2.4 布隆过滤器的其他使用场景

  1. 爬虫过滤已抓到的url就不需要抓取 ,可以使用BF
  2. 垃圾邮件过滤,当用户接收邮件,使用布隆过滤器检测,检测到标识为垃圾邮件。
  3. 维护一个哈希过弱密码列表。当用户注册或更新密码时,使用布隆过滤器检查新密码,检测到提示用户。

1.2.5 布隆过滤器实战

1.2.5.1 redission实现


public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.14.104:6379");
        config.useSingleServer().setPassword("123");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%
        bloomFilter.tryInit(100000000L,0.03);
        //将号码10086插入到布隆过滤器中
        bloomFilter.add("10086");

        //判断下面号码是否在布隆过滤器中
        System.out.println(bloomFilter.contains("123456"));//false
        System.out.println(bloomFilter.contains("10086"));//true
    }
}

1.2.5.2 guava


public class GuavaBloomFilter {
    public static void main(String[] args) {
        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),100000,0.01);

        bloomFilter.put("10086");

        System.out.println(bloomFilter.mightContain("123456"));
        System.out.println(bloomFilter.mightContain("10086"));
    }
}

2. 缓存击穿

2.1 概念

在高并发场景下,当一个缓存key过期时,因为访问这个缓存key 的请求量较大,多个请求同时发现缓存过期,因此多个请求会同时访问数据库来查询最新数据,并且回写缓存,这样会造成应用和数据库的负载增加,性能降低,由于并发较高,甚至会导致数据库被压死。

2.2 解决方案

2.2.1 本地锁

限制一个服务节点一个线程去访问数据库,若节点数过多,此法不适用

2.2.1 分布式锁

保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。

2.2.1 过期时间延长

一般高并发持续期间不应该让缓存过期,比如秒杀场景下,秒杀活动持续10分钟,则过期时间延长为半小时或1小时,避免高并发期间换错过期

3. 缓存雪崩

3.1概念

缓存雪崩指缓存服务器重启或者大量缓存集中在某一个时间段内失效,给后端数据库造成瞬时的负载升高的压力,甚至压垮数据库的情况。

3.2 解决方案

3.2.1 均匀过期

设置不同的过期时间,让缓存失效的时间点尽量均匀。通常可以为有效期增加随机值或者统一规划有效期。

3.2.2 加互斥锁

跟缓存击穿解决思路一致,同一时间只让一个线程构建缓存,其他线程阻塞排队。

3.2.3 缓存永不过期

跟缓存击穿解决思路一致,缓存在物理上永远不过期,用一个异步的线程更新缓存。

3.2.4 双层缓存策略

使用主备两层缓存:

主缓存:有效期按照经验值设置,设置为主读取的缓存,主缓存失效后从数据库加载最新值。

备份缓存:有效期长,获取锁失败时读取的缓存,主缓存更新时需要同步更新备份缓存。

4. 缓存预热

4.1 概念

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候,先查询数据库,然后再将数据回写到缓存。

如果不进行预热, 那么 Redis 初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

4.2 实现方案

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。