【Redis】Redis缓存穿透

缓存穿透 :缓存和数据库中都不存在目标数据,每次请求都到达数据库。如果缓存中没有命中目标,查询就会达到数据库,数据库中也没有目标数据,永远也不会建立缓存,所以每次请求就会到达数据库。

解决方案

方案一 :缓存空对象至redis

优点:实现简单,维护方便

缺点:可能造成短期数据不一致的情况,假设刚缓存一个空值,另一个线程刚好就更新了数据库。

存在额外的内存消耗,需要存储空值

方案二 :布隆过滤

优点:不占用额外的内存空间,没有多余的key

缺点:实现复杂,可能误判

原理:发送一个请求时,会到布隆过滤器中,如果布隆过滤器判断数据不存在,就会直接拒绝请求。如果布隆数据库存在,就会放行。如果到redis缓存中未命中数据,就会到数据库中查询。然后将查询的数据缓存到redis中。如果redis缓存有数据,就直接将数据返回到客户端了。

问题:那么布隆过滤器怎么知道数据是否存在呢?

布隆过滤器判断是否存在数据,基于某一种算法将数据计算出哈希值,然后将哈希值转换成二进制位,最后保存在布隆过滤器的byte数组中。我们判断数据是否存在,其实就是判断对应的位置是0或1,这种存在是概率上的统计,不是百分比准确。当布隆过滤器说数据不存在,那一定是不存在的。当布隆过滤器说数据存在,那不一定真的存在,因为可能会出现哈希冲突。所以这种方式还是有可能会出现缓存穿透的。

案例:基于缓存空值的方案解决缓存穿透

之前的逻辑,如果这个数据在书库中不存在,直接就返回404了,这样会有缓存穿透的问题。

现在的逻辑:如果这个数据不存在,把空值写入到redis中,当再次发起查询时,就会命中缓存。但是并不是到这就结束了,既然将空值写到redis了,就会导致我们从redis中命中时,命中的就不一定是商铺信息了,还有可能是空值,因此命中后还需要对结果做判断。判断这个value是否是null,如果是null,则是之前写入的数据,直接返回null即可;如果不是,则直接返回商品信息。

代码实现

定义一个常量类存放空值过期时间

复制代码
public static final Long CACHE_NULL_TTL = 2L;

实现逻辑

复制代码
@Override
public Result queryById(Long id) {

    // 店铺key的选择要确保唯一,因为店铺都要有一个唯一的id,因此在这里直接使用id作为key。当然我们需要一个前缀
    String key = RedisConstants.CACHE_SHOP_KEY + id;
    String shopJson = stringRedisTemplate.opsForValue().get(key);
    // 2.判断是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        // 3.存在,直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    //*****************************************
    // 判断命中的是否是空值
    if (shopJson != null) {
        // 返回一个错误信息
        return Result.fail("店铺信息不存在!")
    }
    //******************************************

    // 4.不存在,根据id查询数据库
    Shop shop = getById(id);
    // 5.查询数据库不存在,直接返回错误
    if (shop == null) {
        //**********************************************
        // 将空值写入redis,并且有效期不能像真实数据那么长(30分钟)
        stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
        //返回错误信息
        return Result.fail("店铺不存在!");
        //********************************************
    }
    // 6.存在,写入redis
    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
    // 7.返回
    return Result.ok(shop);
}

isNoBlank() 方法判断字符串是否不为空,不为空返回true, 否则返回false。

结语:这两种方案都是防御性的,也就是说别人来攻击你了,你才做出策略。我们也可以采用主动性的策略,如

  1. 增强id的复杂度,不容易让人发现id规律。这样就可以校验id,当传递的id格式不对,就没必要让其往下请求数据了。
  2. 某些业务让其登录后访问,登录后就可以控制请求的速率,在一段时间内发送多少次请求。如某些社交软件登录,密码错误多少次需要等待一段时间才能操作。
相关推荐
2301_7717172118 分钟前
解决mysql报错:1406, Data too long for column
android·数据库·mysql
小江的记录本39 分钟前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka
dvjr cloi43 分钟前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dFObBIMmai1 小时前
MySQL主从同步中大事务导致的延迟_如何拆分大事务优化同步
jvm·数据库·python
szccyw01 小时前
mysql如何限制特定存储过程执行权限_MySQL存储过程安全访问
jvm·数据库·python
czlczl200209252 小时前
利用“延迟关联”优化 MySQL 巨量数据的深分页查询
数据库·mysql
ACP广源盛139246256732 小时前
IX8024与科学大模型的碰撞@ACP#筑牢科研 AI 算力高速枢纽分享
运维·服务器·网络·数据库·人工智能·嵌入式硬件·电脑
Elastic 中国社区官方博客2 小时前
ES|QL METRICS_INFO 和 TS_INFO:为你的时间序列数据建立目录
大数据·数据库·elasticsearch·搜索引擎·信息可视化·全文检索
俺不要写代码3 小时前
数据库:函数
数据库·mysql
2401_882273723 小时前
如何在 CSS 中正确加载本地 JPG 背景图片
jvm·数据库·python