Redis“双重检测锁”机制解决缓存击穿代码实践

1、问题:

当系统中引入redis缓存后,一个请求进来后,会先从redis缓存中查询,缓存有就直接返回,缓存中没有就去db中查询,db中如果有就会将其丢到缓存中,但是有些key对应更多数据在db中并不存在,每次针对此次key的请求从缓存中取不到,请求都会压到db,从而可能压垮db。

比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用大量此类攻击可能 压垮数据库。

2、解决方案:

使用“双重检测锁”机制,从数据库查询数据方法外加锁,锁内再次判断缓存是否有数据。

3、代码实践:

    public R> list(Dish dish){
        List dishDtoList=null;
        String key="dish_"+dish.getCategoryId()+"_"+dish.getStatus();//例:dish_139312313213121323_1
        //使用"双重检测锁"机制查询redis
        //第一次查询redis
        dishDtoList= (List)redisTemplate.opsForValue().get(key);
        //如果存在,直接返回,无需查询数据库
        if (dishDtoList!=null){
            return R.success(dishDtoList);
        }else {
            synchronized (this){
                //第二次查询redis
                List dishDtoList2= (List) redisTemplate.opsForValue().get(key);
                if (dishDtoList2==null){
                    //构造查询条件
                    LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
                    //添加条件,查询状态为1(起售状态)的菜品
                    queryWrapper.eq(Dish::getStatus,1);
                    //添加排序条件
                    queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
                    List list = dishService.list(queryWrapper);
                    dishDtoList = list.stream().map((item) -> {
                        DishDto dishDto = new DishDto();
                        BeanUtils.copyProperties(item,dishDto);
                        Long categoryId = item.getCategoryId();//分类id
                        //根据id查询分类对象
                        Category category = categoryService.getById(categoryId);
                        if(category != null){
                            String categoryName = category.getName();
                            dishDto.setCategoryName(categoryName);
                        }
                        //当前菜品的id
                        Long dishId = item.getId();
                        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
                        lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
                        //SQL:select * from dish_flavor where dish_id = ?
                        log.info("-------查询数据库-------");
                        List dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
                        dishDto.setFlavors(dishFlavorList);
                        return dishDto;
                    }).collect(Collectors.toList());
                    if (dishDtoList==null){
                        //redis缓存穿透解决方案:对空值进行缓存
                        List dishDtoListNull= null;
                        redisTemplate.opsForValue().set(key,dishDtoListNull,10, TimeUnit.SECONDS);
                    }else {
                        //如果不存在,需要查询数据库,将查询到的菜品数据缓存到redis
                        redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);
                    }
                }else {
                    return R.success(dishDtoList2);
                }
            }
            return R.success(dishDtoList);
        }
    }

你可能感兴趣的:(缓存,redis,数据库)