• redis的使用场景


    目录

    1.作为缓存

    1.1 为何使用

    1.2 什么样的数据适合放入缓存

    1.3 使用redis作为缓存

    1.3.1 未使用配置类

    1.3.2 使用配置类

    2.分布式锁

    2.1 压测工具的使用

    2.2 库存项目

    2.2.1 controller层

    2.2.2 dao层

    2.2.3 entity层

    2.2.4 service层

    2.2.5 mapper

    2.2.6 依赖

    2.2.7 测试结果

    2.3 解决方案

    2.3.1 使用 synchronized 或者lock锁 

    2.3.2 使用redisTemplate


    1.作为缓存

    1.1 为何使用

            数据存储在内存中,数据查询速度快。可以分摊数据库压力。

    1.2 什么样的数据适合放入缓存

            查询频率比较高,修改频率比较低。

            安全系数低的数据

    1.3 使用redis作为缓存

    1.3.1 未使用配置类

    注意要将实体类实现序列化:

    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. @TableName(value = "tb_dept")
    5. public class Dept implements Serializable {
    6. @TableId(value = "id",type = IdType.AUTO)
    7. private Integer id;
    8. private String name;
    9. private String realname;
    10. }

    对应依赖:

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.bootgroupId>
    4. <artifactId>spring-boot-starter-data-redisartifactId>
    5. dependency>
    6. <dependency>
    7. <groupId>org.springframework.bootgroupId>
    8. <artifactId>spring-boot-starter-webartifactId>
    9. dependency>
    10. <dependency>
    11. <groupId>mysqlgroupId>
    12. <artifactId>mysql-connector-javaartifactId>
    13. dependency>
    14. <dependency>
    15. <groupId>org.springframework.bootgroupId>
    16. <artifactId>spring-boot-starter-jdbcartifactId>
    17. dependency>
    18. <dependency>
    19. <groupId>com.baomidougroupId>
    20. <artifactId>mybatis-plus-boot-starterartifactId>
    21. <version>3.4.2version>
    22. dependency>
    23. <dependency>
    24. <groupId>org.projectlombokgroupId>
    25. <artifactId>lombokartifactId>
    26. <optional>trueoptional>
    27. dependency>
    28. <dependency>
    29. <groupId>org.springframework.bootgroupId>
    30. <artifactId>spring-boot-starter-testartifactId>
    31. <scope>testscope>
    32. dependency>
    33. dependencies>

    controller层对应代码:

    1. @RestController
    2. @RequestMapping("order")
    3. public class DeptController {
    4. @Resource
    5. private DeptService deptService;
    6. @GetMapping("getById/{id}")
    7. //order/getById/1
    8. //{}可以放多个,由下面的传参函数对应
    9. //@PathVariable:获取请求映射中{}的值
    10. public Dept getById(@PathVariable Integer id){
    11. return deptService.findById(id);
    12. }
    13. @GetMapping("deleteById/{id}")
    14. public String deleteById(@PathVariable Integer id){
    15. int i = deptService.deleteById(id);
    16. return i>0?"删除成功":"删除失败";
    17. }
    18. @GetMapping("insert")
    19. public Dept insert(Dept dept){
    20. Dept insert = deptService.insert(dept);
    21. return insert;
    22. }
    23. @GetMapping("update")
    24. public Dept update(Dept dept){
    25. Dept update = deptService.update(dept);
    26. return update;
    27. }
    28. }

    service层对应代码:

    1. @Service
    2. public class DeptService {
    3. @Resource
    4. private DeptMapper deptMapper;
    5. //当存储的value类型为对象类型使用redisTemplate
    6. //存储的value类型为字符串。StringRedisTemplate
    7. @Autowired
    8. private RedisTemplate redisTemplate;
    9. //业务代码
    10. public Dept findById(Integer id){
    11. ValueOperations forValue = redisTemplate.opsForValue();
    12. //查询缓存
    13. Object o = forValue.get("dept::" + id);
    14. //缓存命中
    15. if(o!=null){
    16. return (Dept) o;
    17. }
    18. Dept dept = deptMapper.selectById(id);
    19. if(dept!=null){
    20. //存入缓存中
    21. forValue.set("dept::"+id,dept,24, TimeUnit.HOURS);
    22. }
    23. return dept;
    24. }
    25. public int deleteById(Integer id){
    26. redisTemplate.delete("dept::"+id);
    27. int i = deptMapper.deleteById(id);
    28. return i;
    29. }
    30. public Dept insert(Dept dept){
    31. int insert = deptMapper.insert(dept);
    32. return dept;
    33. }
    34. public Dept update(Dept dept){
    35. redisTemplate.delete("dept::"+dept.getId());
    36. int i = deptMapper.updateById(dept);
    37. return dept;
    38. }
    39. }

    配置源:

    1. # 配置数据源
    2. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    3. spring.datasource.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai
    4. spring.datasource.username=root
    5. spring.datasource.password=root
    6. #sql日志
    7. mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    8. #连接redis
    9. spring.redis.host=192.168.22*.1**
    10. spring.redis.port=6379

    查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。

    spring框架它应该也能想到。--使用注解即可完成。解析该注解。

    1.3.2 使用配置类

    (1)把缓存的配置类加入

    1. @Bean
    2. public CacheManager cacheManager(RedisConnectionFactory factory) {
    3. RedisSerializer redisSerializer = new StringRedisSerializer();
    4. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    5. //解决查询缓存转换异常的问题
    6. ObjectMapper om = new ObjectMapper();
    7. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    8. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    9. jackson2JsonRedisSerializer.setObjectMapper(om);
    10. // 配置序列化(解决乱码的问题),过期时间600秒
    11. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    12. .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
    13. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
    14. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
    15. .disableCachingNullValues();
    16. RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
    17. .cacheDefaults(config)
    18. .build();
    19. return cacheManager;
    20. }

    (2) 使用开启缓存注解

    (3)使用注解  

    1. //业务代码
    2. //使用查询注解:cacheNames表示缓存的名称 key:唯一标志---dept::key
    3. //先从缓存中查看key为(cacheNames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
    4. @Cacheable(cacheNames = {"dept"},key="#id")
    5. public Dept findById(Integer id){
    6. Dept dept = deptMapper.selectById(id);
    7. return dept;
    8. }
    9. //先删除缓存在执行方法体。
    10. @CacheEvict(cacheNames = {"dept"},key = "#id")
    11. public int deleteById(Integer id){
    12. int row = deptMapper.deleteById(id);
    13. return row;
    14. }
    15. //这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
    16. @CachePut(cacheNames = "dept",key="#dept.id")
    17. public Dept update(Dept dept){
    18. int insert = deptMapper.updateById(dept);
    19. return dept;
    20. }

    2.分布式锁

    使用压测工具测试高并发下带来线程安全问题

    2.1 压测工具的使用

     

    内部配置:

     

     2.2 库存项目

    2.2.1 controller层

    1. @RestController
    2. @RequestMapping("bucket")
    3. public class BucketController {
    4. @Autowired
    5. private BucketService bucketService;
    6. @GetMapping("update/{productId}")
    7. public String testUpdate(@PathVariable Integer productId){
    8. String s = bucketService.updateById(productId);
    9. return s;
    10. }
    11. }

    2.2.2 dao层

    1. //此处写就不需要在启动类使用注解
    2. @Mapper
    3. public interface BucketMapper extends BaseMapper {
    4. public Integer updateBucketById(Integer productId);
    5. }

    2.2.3  entity层

    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. public class Bucket {
    5. @TableId(value = "productId",type = IdType.AUTO)
    6. private Integer productId;
    7. private Integer num;
    8. }

    2.2.4 service层

    1. @Service
    2. public class BucketService {
    3. @Resource
    4. private BucketMapper bucketMapper;
    5. public String updateById(Integer productId){
    6. //查看该商品的库存数量
    7. Bucket bucket = bucketMapper.selectById(productId);
    8. if(bucket.getNum()>0){
    9. //修改库存每次减1
    10. Integer integer = bucketMapper.updateBucketById(productId);
    11. System.out.println("扣减成功!剩余库存数:"+(bucket.getNum()-1));
    12. return "success";
    13. }else {
    14. System.out.println("扣减失败!库存数不足");
    15. return "fail";
    16. }
    17. }
    18. }

    2.2.5  mapper

    1. mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    2. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.qy151wd.dao.BucketMapper">
    4. <update id="updateBucketById" parameterType="int">
    5. update bucket set num=num-1 where productId=#{productId}
    6. update>
    7. mapper>

    2.2.6 依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.bootgroupId>
    4. <artifactId>spring-boot-starter-data-redisartifactId>
    5. dependency>
    6. <dependency>
    7. <groupId>org.springframework.bootgroupId>
    8. <artifactId>spring-boot-starter-webartifactId>
    9. dependency>
    10. <dependency>
    11. <groupId>mysqlgroupId>
    12. <artifactId>mysql-connector-javaartifactId>
    13. dependency>
    14. <dependency>
    15. <groupId>org.springframework.bootgroupId>
    16. <artifactId>spring-boot-starter-jdbcartifactId>
    17. dependency>
    18. <dependency>
    19. <groupId>com.baomidougroupId>
    20. <artifactId>mybatis-plus-boot-starterartifactId>
    21. <version>3.4.2version>
    22. dependency>
    23. <dependency>
    24. <groupId>org.projectlombokgroupId>
    25. <artifactId>lombokartifactId>
    26. <optional>trueoptional>
    27. dependency>
    28. <dependency>
    29. <groupId>org.springframework.bootgroupId>
    30. <artifactId>spring-boot-starter-testartifactId>
    31. <scope>testscope>
    32. dependency>
    33. dependencies>

    2.2.7 测试结果

    我们看到同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。

    2.3 解决方案

    2.3.1 使用 synchronized 或者lock锁 

     对应的service层修改为

    1. @Service
    2. public class BucketService {
    3. @Resource
    4. private BucketMapper bucketMapper;
    5. public String updateById(Integer productId){
    6. //加自动锁
    7. synchronized (this){
    8. //查看该商品的库存数量
    9. Bucket bucket = bucketMapper.selectById(productId);
    10. if(bucket.getNum()>0){
    11. //修改库存每次减1
    12. Integer integer = bucketMapper.updateBucketById(productId);
    13. System.out.println("扣减成功!剩余库存数:"+(bucket.getNum()-1));
    14. return "success";
    15. }else {
    16. System.out.println("扣减失败!库存数不足");
    17. return "fail";
    18. }
    19. }
    20. }
    21. }

    如果搭建了项目集群,那么该锁无效 。

    2.3.2 使用redisTemplate

    (1)使用idea开集群项目

     (2)使用nginx

     (3)测试结果

    发现又出现: 重复数字以及库存为负数。

    (4)解决方法

    service对应代码修改

    1. @Service
    2. public class BucketService {
    3. @Resource
    4. private BucketMapper bucketMapper;
    5. @Autowired
    6. private RedisTemplate redisTemplate;
    7. public String updateById(Integer productId){
    8. ValueOperations forValue = redisTemplate.opsForValue();
    9. Boolean flag = forValue.setIfAbsent("aaa::" + productId, "-----------------");
    10. if(flag){
    11. try{
    12. //查看该商品的库存数量
    13. Bucket bucket = bucketMapper.selectById(productId);
    14. if(bucket.getNum()>0){
    15. //修改库存每次减1
    16. Integer integer = bucketMapper.updateBucketById(productId);
    17. System.out.println("扣减成功!剩余库存数:"+(bucket.getNum()-1));
    18. return "success";
    19. }else {
    20. System.out.println("扣减失败!库存数不足");
    21. return "fail";
    22. }
    23. }finally {
    24. redisTemplate.delete("aaa::"+productId);
    25. }
    26. }
    27. return "服务器正忙,请稍后再试.......";
    28. }
    29. }

    注意此处的测压速度不易太快(推荐使用5秒100个线程)

    经过测压测试后,结果为:

     

     

  • 相关阅读:
    信创环境下Nginx正向代理实现内网发送邮件
    c++编程(11)——string类的模拟实现
    对软件代码坏味道的认识
    java实战:Redis实现查找附近的人
    【React】第十部分 redux
    文心一言api开发者文档,python版ERNIE-3.5-8K-Preview模型调用方法
    《Mycat分布式数据库架构》之ER分片
    【脑机接口开源数据处理包】brainflowBrainFlow是一个库,旨在获取,解析和分析脑电图,肌电图,心电图和其他类型的数据从生物传感器。
    一、Docker容器
    V-Value in fiber(光纤中的V值)
  • 原文地址:https://blog.csdn.net/qq_50896786/article/details/126133110