缓存穿透基本认识

news/2024/6/16 7:20:43 标签: 缓存

我先说一下正常的业务流程:需要查询店铺数据,我们会先从redis中查询,判断是否能命中,若命中说明redis中有需要的数据就直接返回;没有命中就需要去mysql数据库查询,在数据库中查到了就返回数据并把该数据存入redis中,若mysql数据库中也查不到就返回null,并返回错误信息:该信息不存在。

代码是用springboot+mybatis plus +redis+mysql实现的

 下面这是实现业务的代码:

mapper层:

ublic interface ShopMapper extends BaseMapper<Shop> {

}

 service实现类

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {


    @Resource
    private StringRedisTemplate stringRedisTemplate;
@Override
    public Result queryById(Long id) {

        //1.从redis查询数据缓存 ,CACHE_SHOP_KEY为事先写的静态变量是存在redis中的key前缀用于区分不同业务的key, CACHE_SHOP_KEY值为 cahce:shop:
        String key=CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) { //isNOtBlank方法只有有值字符串才会返回true,null和空值都会返回false
            //3.存在,返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        //5.不存在,返回错误
        if (shop==null){
            //将空值缓存到redis
            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);
    }
}

controller层:

@RestController
@RequestMapping("/shop")
public class ShopController {

    @Resource
    public IShopService shopService;

   
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return shopService.queryById(id);
    }

以上就是代码实现,下面在来了解一下什么是缓存穿透:

 正常流程是我们先去redis中查找,redis中没有就会去数据库中找。但可能会有一些人故意或失误的频繁重复的查找一个根本不存在的数据。查找根本不存在数据,流程是先去redis中找,但redis中肯定没有,所以就去数据库中找,但数据库中肯定也没有,当这些人高并发的重复发送查找这个不存在数据的请求,这些请求就会全部发到数据库中,数据库的性能并不算很好,一下子接收这么多请求很容易垮掉,这就是缓存穿透。

 解决办法: 有俩个方法,一个是在redis中存入空值,一个是用布隆过滤器。在这我用的是第一个方法,至于布隆过滤器我会简单说一下工作流程。

  redis缓存空值: 

        流程:前面说了,原本是请求一个根本不存在的数据,会先去redis中找,redis没有再去数据库找,数据库没有,返回null。现在呢,数据库中找不到,不仅仅返回null,还会把请求的数据和null存入redis,这样下次在请求这个不存在的数据就能在redis中找到,不用请求数据库了。我们还要为存入的null设置过期时间,不然会一直占用大量内存。

这样做的优点是:逻辑简单,容易实现;缺点是:浪费内存,当某人请求多个不同的不存在数据时,redis中就会存储很多null的数据,占用内存。

 布隆过滤器:

    流程:原本是请求一个根本不存在的数据,会先去redis中找,在去数据库找。现在是在redis之前在加一个布隆过滤器,请求会先经过布隆过滤器,布隆过滤器会判断请求的数据在数据库中是否存在,存在才会继续往下走,查redis和数据库;判断不存在就直接返回null了。

    布隆过滤器判断方法:计算出数据库中所有数据的hash值,将这些hash值转为二进制位保存到布隆过滤器中,判断过程就是判断布隆过滤器中对应位置的数字是0还是1 。 优点是:内存占用少,没有多余的key。缺点是:实现复杂,存在误判的可能。

下面是用redis缓存空值解决的

public Shop queryWithPassThrough(Long id) {
        //1.从redis查询数据缓存
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) { //isNOtBlank方法只有有值字符串才会返回true,null和空值都会返回false
            //3.存在,返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //shopJson不存在
        //判断查到的数据是否为空值(这个空值指的不是null,是空字符串)
        if (shopJson != null) {
            //返回错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        //5.不存在,返回错误
        if (shop == null) {
            //将空值缓存到redis。参数分别是: redis中存入key ,value,过期时间数字(这里是用的事先写的静态变量,2L),过期时间的单位
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        //6.存在,写入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return shop;

    }

下面让我们启动该项目和postman,来测试一下。

 我们请求id为0的数据,数据库id都是从1开始的,所以不存在0号数据,用postman发送请求成功返回true,下面查看redis客户端看看有没有写入空值

 图中我们可以看到成功写入并存入空值。

并且idea中返回信息表明查询了数据库一次

我们可以多发几次请求,看看会不会都去请求数据库,注意:前面代码中我们设置的该redis缓存的过期时间只有2分钟,要在2分钟内去发送请求才不会去请求数据库。2分钟后会重新查询一次数据库,然后后空值存入redis。

2分钟内发送多次请求,查看idea控制台

 可以看到即使我们发送多次请求,也只返回了一次查询数据库的信息。

这样存储穿透就简单的解决了


http://www.niftyadmin.cn/n/5514256.html

相关文章

openjudge_2.5基本算法之搜索_7221:拯救公主

题目 7221:拯救公主 总时间限制: 1000ms 内存限制: 65536kB 描述 多灾多难的公主又被大魔王抓走啦&#xff01;国王派遣了第一勇士阿福去拯救她。 身为超级厉害的术士&#xff0c;同时也是阿福的好伙伴&#xff0c;你决定祝他一臂之力。你为阿福提供了一张大魔王根据地的地图…

mac读不出来ntfs mac硬盘读不出来盘

新买的Mac电脑由于需要导入旧电脑的数据&#xff0c;因此通常会读取备份硬盘&#xff0c;通过硬盘进行导入。不过由于各种原因&#xff0c;有些mac用户反馈无法正常读取或写入NTFS移动硬盘&#xff0c;下面就通过本篇教程&#xff0c;简单讲述当mac读不出来ntfs&#xff0c;mac…

RabbitMQ-Stream(高级详解)

文章目录 什么是流何时使用 RabbitMQ Stream&#xff1f;在 RabbitMQ 中使用流的其他方式基本使用Offset参数chunk Stream 插件服务端消息偏移量追踪示例 示例应用程序RabbitMQ 流 Java API概述环境创建具有所有默认值的环境使用 URI 创建环境创建具有多个 URI 的环境 启用 TLS…

C#——集合List

list list集合和Arraylist基本一样&#xff0c;只不过list是C#2.0版本新加入的范型类型。list也可以通过索引操作里面的元素&#xff0c;也有对list进行增删改查 概念 Array静态数组 * Arraylist 动态数组 * list集合 * 1. Array是容量是固定的&#xff0c;但是ArrayList和…

FileZilla:不安全的服务器,不支持 FTP over TLS 原因与解决方法

今天在用FileZilla Client连接某个主机的FTP的时候&#xff0c;主机地址、账号、密码、端口确定百分之百正确的情况下&#xff0c;结果报错如下&#xff1a; 状态: 正在解析 x.x.x 的地址 状态: 正在连接 x.x.x.x:21... 状态: 连接建立&#xff0c;等待欢迎消息... 状态: 不安全…

数组中的map方法

JavaScript中的map()方法详解 map()方法经常拿来遍历数组&#xff0c;但是不改变原数组&#xff0c;但是会返回一个新的数组&#xff0c;并且这个新的数组不会改变原数组的长度 注意&#xff1a;有时候会出现这种现象&#xff0c;出现几个undefined const array [1, 4,9, 16…

【端午惊喜】2024年6月6日 docker 国内镜像源集体失效

文章目录 概述中科大镜像源阿里镜像源其他镜像源可用的镜像源写在最后 概述 大家都知道使用docker hub官方镜像需要魔法&#xff0c;虽然大部人有魔法&#xff0c;但是网速也是很慢&#xff0c;还有部分同学没有&#xff0c;全靠国内各大厂商的镜像源&#xff0c;可是端午6.6大…

fastapi搭建的python项目,怎么才能出来API接口文档

使用 FastAPI&#xff0c;你可以轻松生成和测试 API 接口文档。FastAPI 内置了自动生成文档的功能&#xff0c;并且提供了交互式的 API 文档界面。 以下是如何生成和测试 API 接口文档的步骤&#xff1a; 确保项目结构正确&#xff1a; main.py 应该包含 FastAPI 应用实例和路…