• 08_SpingBoot 集成Redis


    • 并发:多个线程同时操作某一个(些)资源,带来数据的不确定性、不稳定性、不安全性
    • 同步:在某一个时刻,只有一个线程访问资源    解决并发问题,性能低下(程序不能让性能过于低下)
    • :唯一     对象监视器
    • 缓存穿(刺)透:缓存有(没有)数据,访问了数据库
    • 缓存雪崩:在某一个时刻,缓存中大部分 同时失效,而此时恰好有很多线程并发访问,导致数据库无法处理这么多访问而瘫痪
    • 评估   缓存数据分时失效    降格处理
    • 多线程:不是计算一个数据 而是描述的是一种状态

    一、Spring Boot 集成Redis单机模式

    1. 案例思路

    完善根据学生id查询学生的功能,先从redis缓存中查找,如果找不到,再从数据库中查找,然后放到redis缓存中

    2. 实现步骤

    首先通过MyBatis逆向工程生成实体bean和数据持久层

     A. 在pom.xml文件中添加redis依赖和其它配置

    1. <!-- 加载spring boot redis包 -->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-data-redis</artifactId>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.mybatis.spring.boot</groupId>
    8. <artifactId>mybatis-spring-boot-starter</artifactId>
    9. <version>2.0.0</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>mysql</groupId>
    13. <artifactId>mysql-connector-java</artifactId>
    14. </dependency>
    1. <build>
    2. <resources>
    3. <resource>
    4. <directory>src/main/java</directory>
    5. <includes>
    6. <include>**/*.xml</include>
    7. </includes>
    8. </resource>
    9. </resources>
    10. <plugins>
    11. <plugin>
    12. <groupId>org.springframework.boot</groupId>
    13. <artifactId>spring-boot-maven-plugin</artifactId>
    14. </plugin>
    15. </plugins>
    16. </build>

    B. 在Spring Boot核心配置文件application.properties中配置redis连接信息

    1. server.port=9005
    2. server.servlet.context-path=/005-springboot-redis
    3. #数据库的配置
    4. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    5. spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    6. spring.datasource.username=root
    7. spring.datasource.password=root
    8. #redis配置
    9. spring.redis.host=192.168.60.130
    10. spring.redis.port=6379
    11. spring.redis.password=123456

    C. 启动redis服务

    01-Centos7安装Redis和启动Redisicon-default.png?t=M5H6https://blog.csdn.net/qq_45037155/article/details/124679838

    03-Redis客户端连接Redis服务器(redis.conf 文件配置没有生效导致redis运行报错Error: Connection reset by peer)icon-default.png?t=M5H6https://blog.csdn.net/qq_45037155/article/details/124683432

    1. [root@Suke /]# cd /usr/local/redis-4.0.6/src/
    2. [root@Suke src]# ./redis-server ../redis.conf &

    D. RedisController 类

    1. @RestController
    2. public class RedisController {
    3. @Autowired
    4. private StudentService studentService;
    5. /*
    6. * http://localhost:9005/005-springboot-redis/springboot/allStudentCount
    7. * */
    8. @GetMapping(value = "/springboot/allStudentCount")
    9. public Object allStudentCount(HttpServletRequest request) {
    10. Long allStudentCount = studentService.queryAllStudentCount();
    11. return "学生总人数:" + allStudentCount;
    12. }
    13. }

    E. StudentService 接口

    1. public interface StudentService {
    2. Long queryAllStudentCount();
    3. }

    F. 在StudentServiceImpl中注入RedisTemplate并修改根据id获取学生的方法

    配置了上面的步骤,Spring Boot将自动配置RedisTemplate,在需要操作redis的类中注入redisTemplate即可。

    注意:Spring Boot帮我们注入RedisTemplate类,泛型里面只能写 <String, String>、<Object, Object>或者什么都不写

    1. @Service
    2. public class StudentServiceImpl implements StudentService {
    3. @Autowired
    4. StudentMapper studentMapper;
    5. @Autowired
    6. RedisTemplate redisTemplate;
    7. @Override
    8. public Long queryAllStudentCount() {
    9. //从redis缓存中获取总人数
    10. Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
    11. //判断是否为空
    12. if (allStudentCount == null) {
    13. //从数据库查询
    14. allStudentCount = studentMapper.selectAllStudentCount();
    15. //将值再存放到redis缓存中
    16. redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
    17. }
    18. return allStudentCount;
    19. }
    20. }

    G. Student 实体类 

    1. public class Student {
    2. private Integer id;
    3. private String name;
    4. private Integer age;
    5. public Integer getId() {
    6. return id;
    7. }
    8. public void setId(Integer id) {
    9. this.id = id;
    10. }
    11. public String getName() {
    12. return name;
    13. }
    14. public void setName(String name) {
    15. this.name = name;
    16. }
    17. public Integer getAge() {
    18. return age;
    19. }
    20. public void setAge(Integer age) {
    21. this.age = age;
    22. }
    23. }

    H. StudentMapper 接口 

    1. @Repository
    2. public interface StudentMapper {
    3. Long selectAllStudentCount();
    4. }

    I. StudentMapper.xml 数据库配置文件

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.suke.springboot.mapper.StudentMapper">
    4. <resultMap id="BaseResultMap" type="com.suke.springboot.model.Student">
    5. <id column="id" jdbcType="INTEGER" property="id"/>
    6. <result column="name" jdbcType="VARCHAR" property="name"/>
    7. <result column="age" jdbcType="INTEGER" property="age"/>
    8. </resultMap>
    9. <sql id="Base_Column_List">
    10. id
    11. , name, age
    12. </sql>
    13. <select id="selectAllStudentCount" resultType="long">
    14. select count(123)
    15. from tb_student
    16. </select>
    17. </mapper>

    J. 启动类Application

    在SpringBoot启动类上添加扫描数据持久层的注解并指定扫描包

    1. @SpringBootApplication
    2. @MapperScan("com.suke.springboot.mapper")//扫描数据持久层
    3. public class Application {
    4. public static void main(String[] args) {
    5. SpringApplication.run(Application.class, args);
    6. }
    7. }

    K. 启动SpringBoot应用,访问测试

     

    L. 打开Redis Desktop Mananger查看Redis中的情况  

    设置20秒后,缓存消失

    M. 在StudentServiceImpl添加一行代码解决乱码

     关系型数据库:数据安全的
     redis:二进制数据安全的   数据直接序列化 存储到redis中,获取的时候,直接把序列化数据拿出来,反序列化成对象 不会产生乱码的

    1. //设置key键,序列化,与功能无关
    2. redisTemplate.setKeySerializer(new StringRedisSerializer());

    1. @Service
    2. public class StudentServiceImpl implements StudentService {
    3. @Autowired
    4. StudentMapper studentMapper;
    5. @Autowired
    6. RedisTemplate redisTemplate;
    7. @Override
    8. public Long queryAllStudentCount() {
    9. //设置key键,序列化,与功能无关
    10. redisTemplate.setKeySerializer(new StringRedisSerializer());
    11. //从redis缓存中获取总人数
    12. Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
    13. //判断是否为空
    14. if (allStudentCount == null) {
    15. //从数据库查询
    16. allStudentCount = studentMapper.selectAllStudentCount();
    17. //将值再存放到redis缓存中
    18. redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
    19. }
    20. return allStudentCount;
    21. }
    22. }

    ​​​​​​二、缓存穿透现象​​​​​​​

    1. RedisController 类

    创建线程池模拟千人并发

    1. @RestController
    2. public class RedisController {
    3. @Autowired
    4. private StudentService studentService;
    5. /*
    6. * http://localhost:9005/005-springboot-redis/springboot/allStudentCount
    7. * */
    8. @GetMapping(value = "/springboot/allStudentCount")
    9. public Object allStudentCount(HttpServletRequest request) {
    10. /*Long allStudentCount = studentService.queryAllStudentCount();
    11. return "学生总人数:" + allStudentCount;*/
    12. //创建线程池
    13. ExecutorService executorService = Executors.newFixedThreadPool(16);
    14. //模拟千人并发
    15. for (int i = 0; i < 1000; i++) {
    16. executorService.submit(new Runnable() {
    17. @Override
    18. public void run() {
    19. studentService.queryAllStudentCount();
    20. }
    21. });
    22. }
    23. return "学生总人数:" + 7;
    24. }
    25. }

     2. StudentServiceImpl 类​​​​​​​

    在控制台输出”查询数据库“和“缓存命中”显示查询结果情况

    1. @Service
    2. public class StudentServiceImpl implements StudentService {
    3. @Autowired
    4. StudentMapper studentMapper;
    5. @Autowired
    6. RedisTemplate redisTemplate;
    7. @Override
    8. public Long queryAllStudentCount() {
    9. //设置key键,序列化,与功能无关
    10. redisTemplate.setKeySerializer(new StringRedisSerializer());
    11. //从redis缓存中获取总人数
    12. Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
    13. //判断是否为空
    14. if (allStudentCount == null) {
    15. System.out.println("----查询数据库-----");
    16. //从数据库查询
    17. allStudentCount = studentMapper.selectAllStudentCount();
    18. //将值再存放到redis缓存中
    19. redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
    20. } else {
    21. System.out.println("-----缓存命中-------");
    22. }
    23. return allStudentCount;
    24. }
    25. }

    3. 启动应用程序,浏览器访问测试

    4. 造成的问题

    Tip:多个线程都去查询数据库,这种现象就叫做缓存穿透,如果并发比较大,对数据库的压力过大,有可能造成数据库宕机。

    三、缓存穿透现象-解决方法

    1. 修改StudentServiceImpl中的代码

    1. @Service
    2. public class StudentServiceImpl implements StudentService {
    3. @Autowired
    4. StudentMapper studentMapper;
    5. @Autowired
    6. RedisTemplate redisTemplate;
    7. @Override
    8. public Long queryAllStudentCount() {
    9. //设置key键,序列化,与功能无关
    10. redisTemplate.setKeySerializer(new StringRedisSerializer());
    11. //从redis缓存中获取总人数
    12. Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
    13. //判断学生总人数是否为空
    14. if (allStudentCount == null) {
    15. //设置同步代码块
    16. synchronized (this) {
    17. //再次从redis缓存中获取学生总人数
    18. allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
    19. //双重检测判断缓存中是否有数据
    20. if (allStudentCount == null) {
    21. System.out.println("----查询数据库-----");
    22. //从数据库查询
    23. allStudentCount = studentMapper.selectAllStudentCount();
    24. //将值再存放到redis缓存中
    25. redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
    26. } else {
    27. System.out.println("-----缓存命中-------");
    28. }
    29. }
    30. } else {
    31. System.out.println("-----缓存命中-------");
    32. }
    33. return allStudentCount;
    34. }
    35. }

    2. 启动应用程序,浏览器访问测试,查看控制台输出

    只有第一个线程查询数据库,其它线程查询Redis缓存,这样的解决的小问题就是第一批进来的用户会有一个等待,但是这样的影响可以忽略

    3. springboot集成Redis阻止缓存穿透,为什么要做双层验证

    • 防止线程获取到cpu执行权限的时候,其他线程已经将数据放到Redis中了,所以再次判断
    • 不能将synchronized范围扩大,因为如果Redis缓存中如果有数据,线程不应该同步,否则影响效率
    • 解决缓存穿透:2次查询缓存,再一次判断
  • 相关阅读:
    手撕Vue-实现事件相关指令
    08-RabbitMQ使用中的常见问题
    verilog 内置语句
    什么是Token?一文带你深入理解Token
    小程序源码:超强大教育培训学校源码微信小程序源码下载,带课件/习题/活动插件,支持小程序与公众号双版本
    科研热点|官宣!2022年JCR分区和影响因子发布时间确定!
    【MySQL】库的操作
    谱本征正交分解 (SPOD)附matlab代码
    VUE day_08(7.26)学子商城项目3
    无框折叠玻璃隔断,上下轨道自由折叠门,集美丽与实用一体,开启空间最大化
  • 原文地址:https://blog.csdn.net/qq_45037155/article/details/125455945