缓存雪崩&缓存击穿&缓存穿透

Mr.LR2022年5月29日
大约 5 分钟

缓存雪崩&缓存击穿&缓存穿透

缓存雪崩

1、什么是缓存雪崩

如果缓存再某一时刻出现大规模的key失效,就会导致大量请求打在了数据库上面,导致数据压力巨大,如果在高并发的情况下,可能瞬间就会导致数据宕机。

2、解决方案

事前

  • 均匀过期:设置不同的过期时间,让缓存失效的时间尽量均匀,避免相同的过期时间导致缓存雪崩,造成大量数据库的访问。
  • 分级缓存:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同。
  • 热点数据缓存永远不过期。

永不过期实际包含两层意思:

物理不过期,针对热点key不设置过期时间

逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建

  • 保证Redis缓存的高可用,防止Redis宕机导致缓存雪崩的问题。可以使用 主从+ 哨兵,Redis集群来避免 Redis 全盘崩溃的情况。

事中

  • 互斥锁:在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降。
  • 使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。

事后

  • 开启Redis持久化机制,尽快恢复缓存数据,一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。

缓存穿透

1、什么是缓存穿透

缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。

2、解决方案

方案一:空对象缓存或者缺省值

一旦发生缓存穿透,我们就可以针对查询的数据,在Redis中缓存一个空值或是和业务层协商确定的缺省值(例如,库存的缺省值可以设为0)。紧接着,应用发送的后续请求再进行查询时,就可以直接从Redis中读取空值或缺省值,返回给业务应用了,避免了把大量请求发送给数据库处理,保持了数据库的正常运行。

方案二:Redis布隆过滤器解决缓存穿透

布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小。

白名单架构说明

image-20220617111705569

3、Redis布隆过滤器使用案例

安装RedisBloom

Redis 在 4.0 之后有了插件功能(Module),可以使用外部的扩展功能,可以使用 RedisBloom 作为 Redis 布隆过滤器插件。

推荐用docker安装

启动

docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest

进入redis-cli

docker exec -it [CONTAINER ID] /bin/bash

代码

/**
 * @Author: LR
 * @Date: 2022/6/17 14:26
 **/
public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        //config.useSingleServer().setPassword("root");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("userList");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%
        bloomFilter.tryInit(100000000L,0.03);
        //将userid admin 插入布隆过滤器
        bloomFilter.add("admin");
        //判断下面userId是否在布隆过滤器中
        System.out.println(bloomFilter.contains("admin22"));//false
        System.out.println(bloomFilter.contains("admin"));//true
    }
}

缓存击穿

1、什么是缓存击穿

缓存击穿跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是某个热点的key失效,大并发集中对其进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。

2、解决方案

物理不过期,针对热点key不设置过期时间

互斥独占锁防止击穿

public User test(Integer userId) {
    User user = null;
    //缓存key
    String key = CACHE_KEY_USER + userId;
    //1 查询redis
    user = (User) redisTemplate.opsForValue().get(key);
    //redis无,进一步查询mysql
    if (user == null) {
        //对于高QPS的优化,进来就先枷锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
        synchronized (UserService.class) {
            user = (User) redisTemplate.opsForValue().get(key);
            //2 二次查询redis还是null,可以去查mysql了
            if (user == null) {
                //从mysql查出来user
                user = this.userMapper.selectByPrimaryKey(userId);
                if (user == null) {
                    return null;
                } else {
                    //mysql有数据,redis没有数据,则需要再写入redis,保证数据的一致性
                    redisTemplate.opsForValue().set(key, user);
                }
            }
        }
    }
    return user;
}

总结

image-20220617144203677

参考

上次编辑于: 2022/6/22 15:53:07
贡献者: liurui-60837