redis的使用场景
Posted 青柠果
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis的使用场景相关的知识,希望对你有一定的参考价值。
目录
1.作为缓存
1.1 为何使用
数据存储在内存中,数据查询速度快。可以分摊数据库压力。
1.2 什么样的数据适合放入缓存
查询频率比较高,修改频率比较低。
安全系数低的数据
1.3 使用redis作为缓存
1.3.1 未使用配置类
注意要将实体类实现序列化:
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "tb_dept")
public class Dept implements Serializable
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private String realname;
对应依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--连接数据源-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mp的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
controller层对应代码:
@RestController
@RequestMapping("order")
public class DeptController
@Resource
private DeptService deptService;
@GetMapping("getById/id")
//order/getById/1
//可以放多个,由下面的传参函数对应
//@PathVariable:获取请求映射中的值
public Dept getById(@PathVariable Integer id)
return deptService.findById(id);
@GetMapping("deleteById/id")
public String deleteById(@PathVariable Integer id)
int i = deptService.deleteById(id);
return i>0?"删除成功":"删除失败";
@GetMapping("insert")
public Dept insert(Dept dept)
Dept insert = deptService.insert(dept);
return insert;
@GetMapping("update")
public Dept update(Dept dept)
Dept update = deptService.update(dept);
return update;
service层对应代码:
@Service
public class DeptService
@Resource
private DeptMapper deptMapper;
//当存储的value类型为对象类型使用redisTemplate
//存储的value类型为字符串。StringRedisTemplate
@Autowired
private RedisTemplate redisTemplate;
//业务代码
public Dept findById(Integer id)
ValueOperations forValue = redisTemplate.opsForValue();
//查询缓存
Object o = forValue.get("dept::" + id);
//缓存命中
if(o!=null)
return (Dept) o;
Dept dept = deptMapper.selectById(id);
if(dept!=null)
//存入缓存中
forValue.set("dept::"+id,dept,24, TimeUnit.HOURS);
return dept;
public int deleteById(Integer id)
redisTemplate.delete("dept::"+id);
int i = deptMapper.deleteById(id);
return i;
public Dept insert(Dept dept)
int insert = deptMapper.insert(dept);
return dept;
public Dept update(Dept dept)
redisTemplate.delete("dept::"+dept.getId());
int i = deptMapper.updateById(dept);
return dept;
配置源:
# 配置数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#连接redis
spring.redis.host=192.168.22*.1**
spring.redis.port=6379
查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。
spring框架它应该也能想到。--使用注解即可完成。解析该注解。
1.3.2 使用配置类
(1)把缓存的配置类加入
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory)
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
(2) 使用开启缓存注解
(3)使用注解
//业务代码
//使用查询注解:cacheNames表示缓存的名称 key:唯一标志---dept::key
//先从缓存中查看key为(cacheNames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
@Cacheable(cacheNames = "dept",key="#id")
public Dept findById(Integer id)
Dept dept = deptMapper.selectById(id);
return dept;
//先删除缓存在执行方法体。
@CacheEvict(cacheNames = "dept",key = "#id")
public int deleteById(Integer id)
int row = deptMapper.deleteById(id);
return row;
//这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
@CachePut(cacheNames = "dept",key="#dept.id")
public Dept update(Dept dept)
int insert = deptMapper.updateById(dept);
return dept;
2.分布式锁
使用压测工具测试高并发下带来线程安全问题
2.1 压测工具的使用
内部配置:
2.2 库存项目
2.2.1 controller层
@RestController
@RequestMapping("bucket")
public class BucketController
@Autowired
private BucketService bucketService;
@GetMapping("update/productId")
public String testUpdate(@PathVariable Integer productId)
String s = bucketService.updateById(productId);
return s;
2.2.2 dao层
//此处写就不需要在启动类使用注解
@Mapper
public interface BucketMapper extends BaseMapper<Bucket>
public Integer updateBucketById(Integer productId);
2.2.3 entity层
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Bucket
@TableId(value = "productId",type = IdType.AUTO)
private Integer productId;
private Integer num;
2.2.4 service层
@Service
public class BucketService
@Resource
private BucketMapper bucketMapper;
public String updateById(Integer productId)
//查看该商品的库存数量
Bucket bucket = bucketMapper.selectById(productId);
if(bucket.getNum()>0)
//修改库存每次减1
Integer integer = bucketMapper.updateBucketById(productId);
System.out.println("扣减成功!剩余库存数:"+(bucket.getNum()-1));
return "success";
else
System.out.println("扣减失败!库存数不足");
return "fail";
2.2.5 mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy151wd.dao.BucketMapper">
<update id="updateBucketById" parameterType="int">
update bucket set num=num-1 where productId=#productId
</update>
</mapper>
2.2.6 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--连接数据源-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mp的依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.2.7 测试结果
我们看到同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。
2.3 解决方案
2.3.1 使用 synchronized 或者lock锁
对应的service层修改为
@Service
public class BucketService
@Resource
private BucketMapper bucketMapper;
public String updateById(Integer productId)
//加自动锁
synchronized (this)
//查看该商品的库存数量
Bucket bucket = bucketMapper.selectById(productId);
if(bucket.getNum()>0)
//修改库存每次减1
Integer integer = bucketMapper.updateBucketById(productId);
System.out.println("扣减成功!剩余库存数:"+(bucket.getNum()-1));
return "success";
else
System.out.println("扣减失败!库存数不足");
return "fail";
如果搭建了项目集群,那么该锁无效 。
2.3.2 使用redisTemplate
(1)使用idea开集群项目
(2)使用nginx
(3)测试结果
发现又出现: 重复数字以及库存为负数。
(4)解决方法
service对应代码修改
@Service
public class BucketService
@Resource
private BucketMapper bucketMapper;
@Autowired
private RedisTemplate redisTemplate;
public String updateById(Integer productId)
ValueOperations<String,String> forValue = redisTemplate.opsForValue();
Boolean flag = forValue.setIfAbsent("aaa::" + productId, "-----------------");
if(flag)
try
//查看该商品的库存数量
Bucket bucket = bucketMapper.selectById(productId);
if(bucket.getNum()>0)
//修改库存每次减1
Integer integer = bucketMapper.updateBucketById(productId);
System.out.println("扣减成功!剩余库存数:"+(bucket.getNum()-1));
return "success";
else
System.out.println("扣减失败!库存数不足");
return "fail";
finally
redisTemplate.delete("aaa::"+productId);
return "服务器正忙,请稍后再试.......";
注意此处的测压速度不易太快(推荐使用5秒100个线程)
经过测压测试后,结果为:
以上是关于redis的使用场景的主要内容,如果未能解决你的问题,请参考以下文章