• 使用redis做数据缓存时存在的问题


    缓存击穿

    大量并发请求同时访问一个在redis中不存在的数据时,就会绕过redis去直接访问底层数据库,对数据库造成极大的访问压力

    解决方案

    使用双重检测锁

    缓存穿透

    当请求访问redis中的某个资源时,若返回的结果为null,则会绕过redis去访问底层数据库,若底层数据库中没有相关数据,则返回结果仍然是null,且底层数据库会将null写入redis中,但由于redis中的数据也是null,redis就失去了缓存的作用,后续并发请求仍然会去访问底层数据库,给数据库造成了访问压力

    解决方案

    若底层数据库中也没有对应数据,则不要返回null,可以返回一个非正常数值或是一个空集合并写入redis中,且写入redis中的无用数据可以设置一个较短的过期时间。如此一来,后续的请求则会直接从redis中获取数据(只不过获得的数据也是无用的)

    缓存雪崩

    redis中的数据在同一时刻大量过期,导致请求这些数据的大量并发请求绕过redis去访问底层数据库,对数据库造成访问压力。

    解决方案

    • 为redis中的数据设置不同的过期时间
    • 在流量洪峰到达前提前缓存热点数据,过期时间设置到流量最低的时段

    项目中的一个代码片段

    public ResultVO listIndexImgs() {
            List<IndexImg> indexImgs = null;
            try {
                //尝试从redis中查询轮播图信息,可能查不到
                String imgsStr = stringRedisTemplate.boundValueOps("indexImgs").get();
                if (imgsStr != null) {
                    //从redis中查询到轮播图信息
                    //代码含义:将json字符串转换为ArrayList集合,
                    JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, IndexImg.class);
                    //readValue 方法可以将json字符串转换成指定的对象
                    indexImgs = objectMapper.readValue(imgsStr, javaType);
                } else {
                    //双重检测锁
                    synchronized (this){
                        //再次查询redis(防止缓存击穿)
                        String s = stringRedisTemplate.boundValueOps("indexImgs").get();
                        if (s==null){
                            //高并发访问的第一次访问会进入此处
                            indexImgs = indexImgMapper.listIndexImgs();
                            if(indexImgs!=null){
                                stringRedisTemplate.boundValueOps("indexImgs").set(objectMapper.writeValueAsString(indexImgs));
                                stringRedisTemplate.boundValueOps("indexImgs").expire(1, TimeUnit.DAYS);
                            }else {
                                //防止缓存穿透
                                List<IndexImg> nullImgsList = new ArrayList<>();
                                stringRedisTemplate.boundValueOps("indexImgs").set(objectMapper.writeValueAsString(nullImgsList));
                                stringRedisTemplate.boundValueOps("indexImgs").expire(10, TimeUnit.SECONDS);
                            }
                        }else {
                            //高并发对同一资源的后续访问会进入这,即后续会直接从redis中查找数据
                            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, IndexImg.class);
                            indexImgs = objectMapper.readValue(s, javaType);
                        }
                    }
                }
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
    
            if (indexImgs!=null){
                return new ResultVO(ResStatus.OK,"success",indexImgs);
            }else {
                return new ResultVO(ResStatus.NO,"fail",null);
            }
    
        }
    
  • 相关阅读:
    在一个字符串中找到第一个只出现一次的字符
    声明数组变量/// 计算所有元素的总和/打印所有元素总和/输出/foreach循环/数组作为函数的参数/调用printArray方法打印
    intellij idea 如何更改编辑器文本字体和大小
    称砝码算法//输入与算法分开
    invalid types 'int[int]' for array subscript// EOF 输入多组数据//如何键盘输入EOF
    scanf和gets的差别
    输入3行字符串/定义flag/while/字符串后要加空格符
    ‘'的单引号/输入字符串/输出单个字符
    窗口迅速关闭的解决办法/scanf/if/for/break
    【笔记】【VSCode】Windows下VSCode编译调试c/c++
  • 原文地址:https://www.cnblogs.com/whyblogs/p/15064432.html
Copyright © 2020-2023  润新知