今天跟着网上的项目做到了缓存数据,背景是用户端是微信小程序,页面显示各种菜品分类,点击分类又能显示对应分类下的菜品信息。
问题说明
用户端展示的菜品数据是存储在mysql当中的,每点击一次分类就需要用户端发送请求到后端查找一次数据库并返回显示在小程序页面上,但是如果用户端访问量比较大,数据库压力会随之增加,数据库查询性能下降,系统反应较慢,导致用户的体验变差,因此要改进查询方式,由此就可以用到缓存。
我理解的缓存就是相当于将需要查询的数据存放到Redis数据库中,当用户端请求查询相关数据时就从Redis数据库中查取。既然缓存就是从我们原本的数据库查询数据转换到从Redis查询数据,那么为什么是从Redis查询数据能实现缓存,缓解数据库查询性能下降的问题呢?
为什么Redis实现数据缓存
主要原因是:
高性能的读写能力:Redis 能读的速度是 110000次/s,写的速度是 81000次/s。这种高性能主要得益于 Redis 将数据存储在内存中,从而显著提高了数据的访问速度。
原子性操作:Redis 的所有操作都是原子性的,这意味着操作要么完全执行,要么完全不执行。
持久化机制:Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,以便在系统重启后能够再次加载使用。
丰富的特性集:Redis 还支持 publish/subscribe(发布/订阅)模式、通知、key 过期等高级特性。
主从复制和高可用性:Redis 支持 master-slave 模式的数据备份,提供了数据的备份和主从复制功能,增强了数据的可用性和容错性。
单线程模型:尽管 Redis 是单线程的,但它通过高效的事件驱动模型来处理并发请求,确保了高性能和低延迟。
因此,Redis 的读写性能比 Mysql 好的多,我们就可以把 Mysql 中的热点数据缓存到 Redis 中,提升读取性能,同时也减轻了 Mysql 的读取压力。缓存可以将常用的数据存储在内存中,以加快数据的读取速度,减少数据库等存储设备的读取次数,从而降低系统的响应时间。
实现菜品缓存
我的这个实例在SpringBoot项目中实现的
想要实现菜品缓存,首先我们要实现能操作Redis,因此我们要创建RedisTemplate对象,RedisTemplate 是 Spring Data Redis 提供的一个高层次的 Redis 操作工具,它封装了对 Redis 的常见操作,使得与 Redis 的交互更加简便。Spring Data Redis 是 Spring 的一部分,提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务,对 Redis 底层开发包进行了高度封装。
连接Redis
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置数据库信息(有密码写密码):
spring:
redis:
host: localhost
port: 6379
# password: 123456
database: 10
创建RedisTemplate对象
该类是Redis相关配置类,项目启动时会扫描该类,其中的@Bean注解能根据类型将RedisConnectionFactory注入,首先创建一个redisTemplate对象,然后传入redis连接工厂对象,设置key的序列化器,可以将key转化为字符串格式,最后返回redisTemplate对象。
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.StringRedisSerializer;
/**
* redis的配置类
*/
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模板对象...");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
实现数据缓存
这里根据项目要实现在用户端查询数据时从缓存中查询数据,因为该项目是根据菜品分类id查询的数据,所以我们可以每个分类下的菜品保存一份缓存数据
(key,value)->(分类id,菜品(字符串))
接收到用户端传来的categoryId时,首先根据categoryId构建规定好的key,根据key查询Redis数据库中是否存在相对应的菜品数据,如果存在直接返回,不需要再查询数据库,如果不存在就需要根据条件查询数据库,查询完毕后再存入Redis缓存,下次直接从Redis缓存中获取。
@Autowired
private RedisTemplate redisTemplate;
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
//构造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);
}
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
//如果不存在,查询数据库,将查询到的数据放入redis中
list = dishService.listWithFlavor(dish);
redisTemplate.opsForValue().set(key,list);
return Result.success(list);
}
结果:
第一次点击,发起请求查询,控制台有输出语句,说明是从数据库查询的数据,Redis中也成功存入数据
再次访问:
清除缓存
这个时候如果我们通过管理端修改数据库的数据,而缓存中的数据又一直存在,用户端再点击查询数据时查询到的依旧是缓存里的未被修改的数据,此时便出现了管理端和客户端的数据不一致的现象,因此,在管理端修改数据同时要清除缓存,用户端再请求数据时就会查询数据库里的数据,获得的便是修改后的数据,修改后的数据便再存入缓存。
在这里,除了查询操作,其他的涉及到修改删除的操作都要清除原来的缓存
这里举一个修改菜品清除缓存的例子
管理端DishController代码:
@Autowired
private RedisTemplate redisTemplate;
@PutMapping
@ApiOperation("更改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品信息{}",dishDTO);
dishService.updateWithFlavor(dishDTO);
//因为修改可能会修改分类,影响两个分类下的数据,所以直接清除所有的缓存数据
//清理缓存数据
Set keys = redisTemplate.keys("dish_*");
redisTemplate.delete(keys);
return Result.success();
}
SpringCache实现
Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,
Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:
-
EHCache
-
Caffeine
-
Redis(常用)
起步依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId> <version>2.7.3</version>
</dependency>
常用注解
在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:
注解 | 说明 |
---|---|
@EnableCaching | 开启缓存注解功能,通常加在启动类上 |
@Cacheable | 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或多条数据从缓存中删除 |
在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。
例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
实现套餐缓存
现在使用注解实现缓存套餐
1.导入相关依赖后在启动类上加入@EnableCaching注解,开启缓存注解功能
2.@Cacheable注解
@Cacheable说明:
作用: 在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中,而CachePut是直接将返回数据存入缓存,没有无数据的情况,所以这里特别说明@Cacheable
value: 缓存的名称,每个缓存名称下面可以有多个key
key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法
@Cacheable实现查询套餐缓存,直接在方法上添加@Cacheable注解,并说明缓存名,构造存在Redis中的key
说明:key的写法如下
#categoryId:将符合条件的参数id作为key
#setmeal.id : #setmeal指的是方法形参的名称, id指的是setmeal的id属性 , 也就是使用setmeal的id属性作为key ;
#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;
#p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
3.@CacheEvict注解
@CacheEvict 说明:
作用: 清理指定缓存
value: 缓存的名称,每个缓存名称下面可以有多个key
key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法
删除套餐为例