苍穹外卖-Day7(缓存菜品/套餐,清除缓存)

(一)缓存菜品

在大量用户访问的高并发场景下(如外卖平台),系统面临每秒数万甚至数十万次请求时,传统数据库(如 MySQL)难以承受直接压力,可能导致响应延迟、服务崩溃。

Redis是通过键值对实现的,对于高并发读 / 写,他的操作复杂度为O(1)。能够有效应对高并发问题。

上图中体现了缓存菜品的实现流程:当前端请求菜品数据时,首先检查缓存(Redis)中是否存在目标数据,存在则直接使用缓存中数据进行返回,不查询数据库;不存在则查询数据库返回数据,并将数据保存在缓存中。

代码实现:在用户菜品接口的Controller层实现缓存查询操作:

java 复制代码
package com.sky.controller.user;

import com.sky.constant.StatusConstant;
import com.sky.entity.Dish;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 根据分类id查询菜品
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        //-------------------------使用redis进行缓存---------
        //构造redis中的key,规则:dish_分类id
        String key ="dish_"+categoryId;
        //查询redis中是否存在菜品数据
        List<DishVO> list=(List<DishVO>) redisTemplate.opsForValue().get(key);
        if(list!=null && list.size()>0){//缓存中已经有该菜品,直接返回,不需要查询数据库
            return Result.success(list);

        }
        //否则不存在,先查询数据库,得到菜品信息,并将菜品存入缓存中(Redis中)
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品

         list = dishService.listWithFlavor(dish);
         //菜品信息存入缓存
         redisTemplate.opsForValue().set(key,list);


        return Result.success(list);
    }

}

(二)清除菜品缓存,保证数据库和缓存中的数据一致性

当管理端修改菜品信息时,此时实时改变的只是数据库数据。会导致缓存中数据与修改后数据库中数据不一致,为了解决这个问题,需要在菜品信息变动时,删除缓存,保证数据一致性。

代码实现:在管理端的菜品接口Controller层实现缓存删除操作

核心代码:

复制代码
/*清除缓存方法*/
public void DeleteRedis(String pattern){
    //Redis不支持直接通过匹配模式进行删除,只能删除一个或多个key
    //先使用Redis提供的keys()方法根据匹配模式获取对应的键,返回的是一个Set集合(无序)
    Set keys=redisTemplate.keys(pattern);

    redisTemplate.delete(keys);
}

//调用方法进行删除缓存

复制代码
//eg1:修改菜品可能之前属于A分类,修改后属于B分类,难以确定,直接全部删除
String pattern="dish_*";  //*为通配符,匹配任何模式
DeleteRedis(pattern);

//eg2:仅删除一个分类

复制代码
String pattern="dish_"+dishDTO.getCategoryId();
//调用方法实现清除
DeleteRedis(pattern);
java 复制代码
package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.mapper.DishMapper;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Set;

@Slf4j
@RestController
@Api(tags = "菜品相关接口")
@RequestMapping("/admin/dish")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /*新增菜品*/
    @PostMapping
    //json格式的数据要使用注解@RequestBody进行封装
    @ApiOperation("新增菜品")
    public Result AddDish(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}", dishDTO);
        dishService.addDishWithFlavor(dishDTO);
        //清除缓存数据
        String pattern="dish_"+dishDTO.getCategoryId();
        //调用方法实现清除
        DeleteRedis(pattern);
         return Result.success();
    }


    /*菜品分页查询*/
    @GetMapping("/page")
    public Result<PageResult> PageDish(DishPageQueryDTO dishPageQueryDTO){
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        return Result.success(dishService.PageDish(dishPageQueryDTO));

    }

    /*批量删除菜品
    * 请求类型:Query 参数
    * 注意,这里是对集合进行处理,与仅删除单个不同:若使用String ids还需要手动处理获取
    * List<Long> ids 添加注解@RequestParam 实现通过spring mvn自动解析参数进行处理,*/
    @DeleteMapping
    public Result DeleteDish(@RequestParam List<Long> ids){
        log.info("当前需要删除的菜品id:{}",ids);

        dishService.DeleteDish(ids);
        //直接全部清除缓存(批量删除包含的菜品可能属于一个分类,也可能属于不同分类,耦合度较高,直接全清除)
        // Redis 的 DEL 命令用于删除一个或多个键。如果键不存在,DEL 会忽略该键,不会报错
        String pattern="dish_*";
        DeleteRedis(pattern);
        return Result.success();
    }
    /*根据id查询菜品 及其对应口味*/
    @GetMapping("/{id}")
    public Result<DishVO> GetByDishId(@PathVariable Long id){
        log.info("根据id查询菜品 及其对应口味:{}",id);
       DishVO getdish=dishService.GetByDishId(id);
        return Result.success(getdish);
    }
    /*菜品起售停售*/
    @PostMapping("/status/{status}")
    public Result UpdateDishStatus(@PathVariable Integer status,Long id){
        log.info("修改id:{}的状态为:{}",id,status);
        dishService.UpdateDishStatus(status,id);
        //没有提供菜品分类id,查询会浪费时间,直接全部删除
        String pattern="dish_*";
        DeleteRedis(pattern);


        return Result.success();
    }

    /*修改菜品*/
    @PutMapping
    public Result UpdateDish(@RequestBody DishDTO dishDTO){
        log.info("修改菜品及其口味:{}", dishDTO);
        dishService.UpdateDish(dishDTO);
        //修改菜品可能之前属于A分类,修改后属于B分类,难以确定,直接全部删除
        String pattern="dish_*";
        DeleteRedis(pattern);

        return Result.success();
    }

    /*根据分类id查询菜品*/
    @GetMapping("/list")
    public  Result<List<Dish>> GetDIshBycateId(Long categoryId){
        log.info("根据分类id查询菜品,查询id:{}",categoryId);
        return Result.success(dishService.GetDishBycateId(categoryId));
    }

    /*清除缓存方法*/
    public void DeleteRedis(String pattern){
        //Redis不支持直接通过匹配模式进行删除,只能删除一个或多个key
        //先使用Redis提供的keys()方法根据匹配模式获取对应的键,返回的是一个Set集合(无序)
        Set keys=redisTemplate.keys(pattern);

        redisTemplate.delete(keys);
    }

}

//出现问题:能够添加缓存,但是修改菜品信息后无法清除缓存数据?

解决:在Redis的配置类中要实现序列化,才能正确匹配模式,且要记得添加@Bean将配置类交给SpringBoot进行管理,否则设置的序列化无法被使用:

redis 中我存入了数据,为什么获取不到_redis存进去了但是读不到-CSDN博客

java 复制代码
package com.sky.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/*配置类实现对Redis配置*/
@Configuration
@Slf4j
public class RedisConfiguration {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){//注入Redis的连接工厂对象
        log.info("开始创建Redis模板对象");
        RedisTemplate redisTemp = new RedisTemplate();
        //设置Redis的连接工厂对象(这个连接工厂对象 是由Starter创建好放入Spring容器中)
        redisTemp.setConnectionFactory(redisConnectionFactory);
//        //设置redis key 的序列化器
//        redisTemp.setKeySerializer(new StringRedisSerializer());

        // **设置 Key 使用 String 序列化,避免 JDK 序列化**
        redisTemp.setKeySerializer(new StringRedisSerializer());
        //redisTemp.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        // **设置 HashKey 和 HashValue 也使用 String 方式存储**
        redisTemp.setHashKeySerializer(new StringRedisSerializer());
       // redisTemp.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemp.afterPropertiesSet();

        return redisTemp;
    }

}

(三)使用Spring cache缓存套餐

通过在相应的方法上添加缓存注解,可以使用Spring cache ,进一步简化缓存代码的开发

(3.1)Spring cache的入门案例1

java 复制代码
@GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
//-------------添加注解实现缓存存储--------------------------
   // @CachePut(cacheNames="usercache",key="abc")  //此时存储到spring cache中的key为usercache::abc
 @CachePut(cacheNames="usercache",key="#categoryId")  //此时存储到spring cache中的key为usercache::categoryId  //spring EL利用反射机制来访问对象的属性,调用方法等,实现动态存储 
    public Result<List<DishVO>> list(Long categoryId) {
      
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品

         List<DishVO> list = dishService.listWithFlavor(dish);
        

        return Result.success(list);
    }

: 表示下一层(cache也有树结构)

(1)@CachePut(cacheNames="usercache",key="#categoryId") //此时存储到spring cache中的key为usercache::categoryId //spring EL利用反射机制来访问对象的属性,调用方法等,实现动态存储

(2)@CachePut(cacheNames="usercache",key="#result") //result表示当前方法的返回值

若是返回值为User对象,其中有id属性可以写为@CachePut(cacheNames="usercache",key="#result.id")

(3)@CachePut(cacheNames="usercache",key="#p0") p0表示取当前行参中的第一个参数,p1表示第二个,依次类推;

(2)删除缓存

@CacheEvict(cacheNames="usercache", allEntries=true)//删除usercache下所有键值对

(2) CacheEvict(cacheNames="usercache", key="#result.id")删除指定键值对

相关推荐
技术猴小猴3 小时前
如何使用Python实现LRU缓存
python·spring·缓存
Roye_ack3 小时前
【黑马点评 - 实战篇01】Redis项目实战(Windows安装Redis6.2.6 + 发送验证码 + 短信验证码登录注册 + 拦截器链 - 登录校验)
数据库·spring boot·redis·缓存·mybatisplus·session·黑马点评
智海观潮17 小时前
Spark RDD详解 —— RDD特性、lineage、缓存、checkpoint、依赖关系
大数据·缓存·spark
野犬寒鸦17 小时前
从零起步学习Redis || 第十章:主从复制的实现流程与常见问题处理方案深层解析
java·服务器·数据库·redis·后端·缓存
梁辰兴18 小时前
计算机操作系统:进程同步
网络·缓存·操作系统·进程·进程同步·计算机操作系统
weixin_445476681 天前
从“用框架”到“控系统”———架构通用能力(模块边界、分层设计、缓存策略、事务一致性、分布式思维)
分布式·缓存·架构
居安思危_Ho1 天前
RK平台Uniapp自启动缓存问题解决
android·缓存·uni-app·rk平台·uniapp资源文件
没有bug.的程序员1 天前
分布式缓存架构:从原理到生产实践
java·分布式·缓存·架构·分布式缓存架构
虫师c1 天前
分布式缓存实战:Redis集群与性能优化
redis·分布式·缓存·redis集群·高可用架构·生产环境·数据分片