使用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);
        }

    }
原文地址:https://www.cnblogs.com/whyblogs/p/15064432.html