SpringBoot集成redis
Redis
特点:
- 基于key-value进行存储;
- 支持多种数据结构:string(字符串); list(集合); hash(哈希); set(集合); zset(有序集合)
- 支持持久化,通过内存进行存储,也可以存到硬盘里面
- 支持过期时间,支持事务
- 使用场景,一般来讲,把经常进行查询,不经常修改,不是特别重要的数据放到redis中作为缓存,比如首页的一些数据,但是例如财务数据这些重要的数据,不能放在redis中做缓存。
一、集成redis
1、在pom中导入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、在properties或yml文件中配置redis连接信息
yml文件引入格式
spring:
redis:
database: 0 #Redis数据库索引(默认为0)
host: 127.0.0.1 #Redis服务器地址
port: 6379 #Redis服务器连接端口
password: #Redis服务器连接密码(默认为空)
3、在业务中(servcieImpl)引入RedisTemplate
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
4、将查询到的信息保存到redis中
//查询全部组别的方法
@Override
public List<Groups> selectAll() {
//查询的时候直接查询缓存中的Groups数据
List<Groups> groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
//判断缓存中有没有Groups
if(null == groups){
//如果没有就查询数据库
groups = groupsMapper.selectAll();
//然后将查询回的数据放在缓存中,下一个查询者就会从缓存获取Groups
redisTemplate.opsForValue().set("allGroups",groups);
}
return groups;
}
5、将redis中存入的key值,变成可读字符串
//将redis中存入的key值,变成可读字符串
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
整体如下
//查询全部组别的方法
@Override
public List<Groups> selectAll() {
//将redis中存入的key值,变成可读字符串
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//查询的时候直接查询缓存中的Groups数据
List<Groups> groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
//判断缓存中有没有Groups
if(null == groups){
//如果没有就查询数据库
groups = groupsMapper.selectAll();
//然后将查询回的数据放在缓存中,下一个查询者就会从缓存获取Groups
redisTemplate.opsForValue().set("allGroups",groups);
}
return groups;
}
注意: 使用redis缓存实体信息,实体类必须被序列化(实现Serializable) 接口
二、redis的内存穿透
redis解决的问题就是减轻数据库的压力,让数据库的内容保存到redis中,但是在并发条件下判断条件没有起到拦截的效果,第一次所有的请求依然会请求到数据库,虽然redis有数据。
三、内存穿透的解决措施
给查询方法加上synchronized锁
//判断缓存中有没有Groups
if(null == groups){
//加锁
synchronized (this){
//如果缓存中有数据,就查询缓存
groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
if(null == groups){
//如果没有就查询数据库
groups = groupsMapper.selectAll();
//然后将查询回的数据放在缓存中,下一个查询者就会从缓存获取Groups
redisTemplate.opsForValue().set("allGroups",groups);
}
}
}
最后serviceimpl代码如下
//查询全部组别的方法
@Override
public List<Groups> selectAll() {
//将redis中存入的key值,变成可读字符串
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//查询的时候直接查询缓存中的Groups数据
List<Groups> groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
//判断缓存中有没有Groups
if(null == groups){
//加锁
synchronized (this){
//如果缓存中有数据,就查询缓存
groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
if(null == groups){
//如果没有就查询数据库
groups = groupsMapper.selectAll();
//然后将查询回的数据放在缓存中,下一个查询者就会从缓存获取Groups
redisTemplate.opsForValue().set("allGroups",groups);
}
}
}
return groups;
}
四、测试
在controller中测试这个方法
@GetMapping("selectAll")
@ApiOperation(value = "查询全部即时组单子")
public R selectAll(){
ExecutorService executorService = Executors.newFixedThreadPool(20);
for(int i = 0;i<10000;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
groupsService.selectAll();
}
});
}
return R.ok(groupsService.selectAll());
}
在方法中,创建一个线程池,实现Runnable接口,重写run方法,模拟10000个用户同时访问,调用service中的selectAll方法
public List<Groups> selectAll() {
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
List<Groups> groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
if(null == groups){
synchronized (this){
groups = (List<Groups>) redisTemplate.opsForValue().get("allGroups");
if(null == groups){
groups = groupsMapper.selectAll();
redisTemplate.opsForValue().set("allGroups",groups);
}
}
}
return groups;
}
来说一下大概流程,当controller调用service中的selectAll方法时,先去查询缓存返回一个groups集合,然后到达第一层if判断,此时groups为空,10000个访问全部进入第一个if判断,此时遇到synchronized锁,锁只让第一个访问进入,再一次查询redis,然后进行第二个if判断,发现redis返回还是空,然后就查询数据库,返回了数据groups,然后将groups放在redis中,之后的9999个访问就直接查询redis了,这样就达到了减轻数据库压力的目的。