高并发下缓存失效问题的原因和解决方案

78 阅读2分钟

1、缓存失效的三大问题

正常情况下数据缓存原理

image.png

1.1、缓存穿透

  1. 解释 是指某些恶意攻击者会使用不存的数据,请求查询接口,就会出现缓存不存在此数据,然后去查询数据库,数据库中也不存在此数据,此数据也不会放入到缓存中,这样的情况下攻击者会发送大量请求,就会造成所有查询数据的请求都会直接跳过缓存,查询数据库,导致数据库压力过大。
  2. 特点
  • 查询的数据不存在
  1. 原理如图所示:

image.png

1.2、缓存雪崩

  1. 解释 是指大量的缓存数据的有效期相同,在某一时刻有大量的请求访问数据时,这时缓存数据全部失效,大量的请求就会查询数据库,导致数据库压力过大。
  2. 特点:
  • 大量的请求查询不同的数据
  • 不同的数据缓存失效
  1. 原理如图所示: image.png

1.3、缓存击穿

  1. 解释 是指某个缓存数据恰好失效时,突然大量的请求查询此数据,就会出现缓存中查不到此数据,大量的请求就会查询数据库。
  2. 特点:
  • 大量的请求查询同个的数据
  • 一个数据缓存失效
  1. 原理如图所示:

image.png

2、解决方案

以下使用此方法作为实例讲解,这时一个查询分类详情的方法

public CategoryEntity getInfoCache(Long catId) {
    ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
    String key = "category:" + catId;
    String value = strOpt.get(key);
    if (StrUtil.isNotEmpty(value)){
        return JSONObject.parseObject(value, CategoryEntity.class);
    }
    CategoryEntity categoryEntity = baseMapper.selectById(catId);
    if (Objects.isNull(categoryEntity)){
        return null;
    }
    strOpt.set(key, JSONObject.toJSONString(categoryEntity));
    return categoryEntity;
}

2.1、缓存穿透的解决方案

2.1.1 将不存在的数据也放入缓存

public CategoryEntity getInfoCache(Long catId) {
    ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
    String key = "category:" + catId;
    String value = strOpt.get(key);
    if (StrUtil.isNotEmpty(value)){
        return JSONObject.parseObject(value, CategoryEntity.class);
    }
    CategoryEntity categoryEntity = baseMapper.selectById(catId);
    // 将不存在的分类也缓存
    strOpt.set(key, JSONObject.toJSONString(categoryEntity));
    return categoryEntity;
}

2.1.2使用布隆过滤器

2.2缓存雪崩解决方案

public CategoryEntity getInfoCache(Long catId) {
    ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
    String key = "category:" + catId;
    String value = strOpt.get(key);
    if (StrUtil.isNotEmpty(value)){
        return JSONObject.parseObject(value, CategoryEntity.class);
    }
    CategoryEntity categoryEntity = baseMapper.selectById(catId);
    // 设置随机的失效时间
    strOpt.set(key, JSONObject.toJSONString(categoryEntity), RandomUtil.randomInt(), TimeUnit.MINUTES);
    return categoryEntity;
}

2.3缓存击穿解决方案

public CategoryEntity getInfoCache(Long catId) {
    ValueOperations<String, String> strOpt = redisTemplate.opsForValue();
    String key = "category:" + catId;
    String value = strOpt.get(key);
    if (StrUtil.isNotEmpty(value)){
        return JSONObject.parseObject(value, CategoryEntity.class);
    }
    // 使用同步锁,保证只有一个线程访问数据库,防止缓存击穿
    synchronized (this) {
        // 拿到锁之后,再次判断缓存中是否有数据,如果有直接返回,避免其他线程重复查询数据库
        if (StrUtil.isNotEmpty(strOpt.get(key))){
            return JSONObject.parseObject(value, CategoryEntity.class);
        }
        CategoryEntity categoryEntity = baseMapper.selectById(catId);
        // 设置随机的失效时间
        strOpt.set(key, JSONObject.toJSONString(categoryEntity), RandomUtil.randomInt(), TimeUnit.MINUTES);
        return categoryEntity;
    }
}