• 非关系型数据库技术课程 第十一周作业(SpringBoot项目中使用Redis作为数据缓存,Redis的缓存机制,数据一致性、缓存穿透和缓存雪崩等问题的处理)


    一、要求

    创建一个 SpringBoot 项目,对 myschool 数据库做操作,要求:

    • 项目名 ” week11_redis_ 学号 ” ;
    • 整合 Mybatis 框架, 或使用 MybatisPlus ;
    • 整合 s SpringDataRedis 框架;
    • 对 student 表进行操作 ,在学生表中插入自己的数据 ,实现根据 id 查询 、更新和删除学生的功能 ;
    • 创建相应的Pojo 层、Mapper 层、Service 层、Controller 层;
    • 在各层中,创建针对user 表操作的类或接口;
    • 使用 redis 作为数据缓存,并考虑数据一致性、缓存穿透和缓存雪崩等问题的处理
    • 启动服务器, 测试控制器中的方法, 可以使用 Postman 进行测试或自己模拟数据进行测试 ;
    • 将运行结果截图,保存在实验报告 中;
    • 将项目和实验报告一起提交。

    二、知识总结

    1.Redis 缓存

    缓存(cache),原始意义是指访问速度比一般随机存取存储器(RAM)快的一种高速存储器,通常它不像系统主存那样使用 DRAM 技术,而使用昂贵但较快速的 SRAM 技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一(摘自百度百科)。

    Redis 因读写性能较高,它非常适合作为存贮数据的临时地方、成为数据交换的缓冲区,因此在一些大型互联网应用中,Redis 常用来进行数据缓存处理。

    下图是项目中使用 Redis 作为 MySQL 缓存的一般流程。
    在这里插入图片描述
    Redis 作为缓存,给系统带来了一些好处:

    • 降低后端负载
    • 提高读写效率,降低响应时间

    但同时,在项目中使用缓存也会带来一些问题:

    • 数据一致性问题:redis 和 mysql 如何保存数据一致性
    • 代码维护成本:处理缓存穿透、缓存雪崩等问题
    • 运维成本:redis 集群的维护

    这些问题是 Redis 作为缓存时必须要考虑的。

    Redis作为MySQL缓存基本代码逻辑实现

    //根据id查询学生信息
        public Student findStudentById(Long id){
            //1.查看Redis缓存中是否有数据
            Student student =getStudentByRedis(id);
    
            //2.如果Redis中有该学生,则返回
            if (student !=null){
                System.out.println("Redis缓存中查询到此学生");
                return student;
            }
    
            // 3.Redis中没有,则到mysql中查询,
            // 缓存穿透处理:如果mysql中也没有,则将空对象写入redis
            System.out.println("Redis缓存中没有此学生");
            student = studentMapper.findStudentById(id);
            if(student==null){
                System.out.println("Mysql中也没有此学生");
                Student s = new Student();
                s.setId(id);
                saveToRedis(s);
            }
            else{
                System.out.println("Mysql中查询到此学生");
                saveToRedis(student);
            }
            return student;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2. 数据一致性问题

    2.1 Redis 缓存更新策略

    从 Redis 的角度来说,它的缓存更新策略一般有 3 种,如下表:
    在这里插入图片描述
    在实际的应用中,根据不同的需缓存处理的数据性质,数据的一致性需求存在以下两种情况:

    • 弱一致性需求:如一些商品类型、客户类型、店铺类型等表示某种类型的数据,
      一般变动比较少,对这些数据进行缓存处理时,可以使用内存淘汰机制;
    • 强一致性需求:如商品库存、店铺主页、促销、优惠券等数据,经常发生变化,对这些数据进行缓存处理时,可以使用主动更新策略,并以超时剔除作为兜底方案。

    2.2 主动更新策略

    主动更新策略即更新数据库的同时更新 Redis 缓存,具体实现时,一般使用以下方案:

    • 删除缓存更新数据库时,删除缓存,让缓存失效,查询时再更新缓存;
    • 保证数据的原子性:单体系统,将缓存与数据库操作放在一个事务,分布式系统,则利用 TCC 等分布式事务方案;
    • 减少并发操作带来的数据错误先操作数据库,再删除缓存
    • 使用超时剔除机制:当数据写入缓存时,一定设定超时时间

    数据一致性问题处理基本代码逻辑:

    数据一致性处理:

    • 1.数据写入redis时,设置key的超时时间
    • 2.修改数据时,==先修改mysql,再删除redis缓存 ==
    • 3.开启事务:保证正确事务的提交
    //根据id修改用户信息
        @Transactional   //1:开启事务
        public String updateStudentById(Student student) {
            Long id = student.getId();
            if (id == null) {
                return "学生id不能为空";
            }
            //2. 先更新mysql数据库
            studentMapper.updateStudentById(student);
            //3. 后删除缓存
            String key="student:"+id;
            redisTemplate.delete(key);
            return "更新成功";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在将数据存入Redis缓存时设置超时时间如:

    //修改 1:设置key的过期时间为6分钟
    redisTemplate.expire(key,360, TimeUnit.SECONDS);
    
    • 1
    • 2

    3. 缓存穿透

    3.1 什么是缓存穿透

    缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,缓存永远不会生效。这样,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。具体见下图。
    在这里插入图片描述

    3.2 解决方案

    常用的缓存穿透的解决方案包括:

    • 对空值进行缓存:即使一个查询返回的数据为空,仍然把这个空结果(null)进行缓存,同时还可以对空结果设置较短的过期时间。这种方法实现简单,维护方便,但是会额外的内存消耗。具体见下图;
      在这里插入图片描述
    • 采用布隆过滤器:(布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
    • 进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
    • 增强 id 的复杂度,避免被猜测 id 规律
    • 做好数据的基础格式校验
    • 加强用户权限校验

    缓存穿透问题处理(对空值进行缓存)相关代码逻辑:

    if(student==null){
                System.out.println("Mysql中也没有此学生");
                Student s = new Student();
                s.setId(id);
                saveToRedis(s);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4. 缓存雪崩

    4.1 什么是缓存雪崩

    缓存雪崩是指在同一时段大量的缓存 key 同时失效,或者 Redis 服务宕机,导致大量请求到达数据库,带来巨大压力。具体如下图所示:
    在这里插入图片描述

    4.2 解决方案

    常用的缓存雪崩的解决方案包括:

    • 给不同的 Key 的 TTL 添加随机值
    • 利用 Redis 集群提高服务的可用性
    • 给缓存业务添加降级限流策略
    • 给业务添加多级缓存

    缓存雪崩的解决方案(给不同的 Key 的 TTL 添加随机值)相关代码逻辑:

    //保存Student信息到Redis,使用hash类型
        public void saveToRedis(Student student) {
            //设置key: student:ID
            String key="student:"+student.getId();
            //各字段的值都存入Redis
            redisTemplate.opsForHash().put(key,"sname",student.getSname()+"");
            redisTemplate.opsForHash().put(key,"dept",student.getDept()+"");
            redisTemplate.opsForHash().put(key,"age",student.getAge()); //!!! Age为Int类型不用+“”
    
            //设置key的过期时间为6分钟
    //        redisTemplate.expire(key,360, TimeUnit.SECONDS);
    
            //缓存雪崩处理:创建一个随机的KEY 的有效期
            int expiredTime=360+new Random().nextInt(100);
            System.out.println("过期时间: "+expiredTime);
            redisTemplate.expire(key,expiredTime, TimeUnit.SECONDS);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5. 项目创建

    创建的SpringBoot项目需要整合Mybatis 框架、SpringDataRedis 框架;同时需要Mysql的驱动等;
    因此创建SpringBoot项目时需要勾选以下依赖:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    具体的创建步骤、注意事项及可能的报错处理可参考:软件工程综合实践课程第十一周作业( SpringBoot整合Mybatis完成CRUD操作并使用接口调试工具对接口进行测试)中“创建SpringBoot项目”部分

    6、接口调试工具的基本使用

    可参考:软件工程综合实践课程第十一周作业( SpringBoot整合Mybatis完成CRUD操作并使用接口调试工具对接口进行测试)中“接口测试工具的基本使用方法”部分

    三、项目结构

    在这里插入图片描述

    四、完整参考代码

    com.example.config包

    com.example.config.RedisConfig

    package com.example.config;
    
    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.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
            // 创建RedisTemplate对象
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            // 设置连接工厂
            template.setConnectionFactory(connectionFactory);
            // 创建JSON序列化工具
            GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
            // 设置Key的序列化
            template.setKeySerializer(RedisSerializer.string());
            template.setHashKeySerializer(RedisSerializer.string());
            // 设置Value的序列化
            template.setValueSerializer(jsonRedisSerializer);
            template.setHashValueSerializer(jsonRedisSerializer);
            // 返回
            return template;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    com.example.pojo包

    com.example.pojo.Student

    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * @projectName: week11_redis_ 
     * @package: com.example.pojo
     * @className: Student
     * @author: GCT
     * @description: TODO
     * @date: 2022/11/11 20:38
     * @version: 1.0
     */
    
    @Data
    @NoArgsConstructor //自动生成无参构造函数
    @AllArgsConstructor
    public class Student {
        Long id;
        String sname;
        String dept;
        int age;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    com.example.mapper包

    com.example.mapper.StudentMapper

    interface类型文件

    package com.example.mapper;
    
    import com.example.pojo.Student;
    import com.example.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    
    import java.io.IOException;
    import java.util.List;
    
    @Mapper
    public interface StudentMapper {
    
        //   直接使用@Select()注解
        @Select("SELECT * FROM student")
        public List<Student> getAllStudentMap();
    
        // 该方法使用了带一个参数的查询语句,返回一条记录
        public Student findStudentById(Long id);
    
    //    根据传入的id数据查找出一个或多个学生信息
        public List<Student> findStudentByIds(Long[] ids);
    
        //更新用户信息
        public int updateStudentById(Student student);
    
        // 该方法插入一条记录,带参数,更新操作一定要提交事务
        public int addStudent(Student student);
    
        //    根据id删除记录
        public int deleteStudentById(Long id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    com/example/mapper/StudentMapper.xml

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.mapper.StudentMapper">
        
        <select id="findStudentById" resultType="Student" parameterType="Long">
            SELECT * FROM student WHERE id=#{0}
        select>
    
        
        <select id="findStudentByIds" resultType="Student" parameterType="Long[]" >
            SELECT * FROM student WHERE id IN
            <foreach collection="array" item="id" open="(" close=")" separator=",">
                #{id}
            foreach>
        select>
    
    
        <update id="updateStudentById"  parameterType="Student">
            UPDATE student set sname=#{sname},dept=#{dept},age=#{age}
            where id=#{id}
        update>
    
        <insert id="addStudent" parameterType="Student">
            
            <selectKey keyProperty="id" order="AFTER" resultType="Long">
                select LAST_INSERT_ID()
            selectKey>
            INSERT INTO student SET sname=#{sname},dept=#{dept},age=#{age}
        insert>
    
        
        
        <delete id="deleteStudentById" parameterType="Long">
            DELETE FROM student WHERE id=#{id}
        delete>
    
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    com.example.service包

    com.example.service.StudentService

    interface类型文件

    package com.example.service;
    
    import com.example.pojo.Student;
    
    import java.io.IOException;
    import java.util.List;
    
    public interface StudentService {
    
        public Student findStudentById(Long id);
    
        //    根据传入的id数据查找出一个或多个学生信息
        public List<Student> findStudentByIds(Long[] ids);
    
    
        public String updateStudentById(Student student);
    
        public List<Student> getAllStudent();
    
        public int addStudent(Student student);
    
    //    根据id删除学生
        public String deleteStudentById(Long id);
    
        //存储Student对象到Redis中
        void saveToRedis(Student student);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    com.example.service.impl包

    com.example.service.impl.StudentServiceImpl
    package com.example.service.impl;
    
    import com.example.mapper.StudentMapper;
    import com.example.pojo.Student;
    import com.example.service.StudentService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @projectName: week11_redis_ 
     * @package: com.example.service.impl
     * @className: StudentServiceImpl
     * @author: GCT
     * @description:
     *  数据一致性处理:
     *  1.数据写入redis时,设置key的超时时间,
     *  2.修改数据时,先修改mysql,再删除redis缓存
     *  3.开启事务:保证正确事务的提交
     *
     *  缓存穿透和缓存雪崩处理方案:
     *  缓存穿透处理:如果mysql中也没有,则将空对象写入redis进行缓存
     *  缓存雪崩处理 :为存入Redis数据库进行缓存的键值对创建一个随机的Key的有效期
     * @date: 2022/11/11 20:39
     * @version: 1.0
     */
    @Service
    public class StudentServiceImpl implements StudentService {
    //    @Resource
         @Autowired
        private StudentMapper studentMapper;
        @Autowired
        private RedisTemplate redisTemplate;
    
        //根据id查询学生信息
        public Student findStudentById(Long id){
            //1.查看Redis缓存中是否有数据
            Student student =getStudentByRedis(id);
    
            //2.如果Redis中有该学生,则返回
            if (student !=null){
                System.out.println("Redis缓存中查询到此学生");
                return student;
            }
    
            // 3.Redis中没有,则到mysql中查询,
            // 缓存穿透处理:如果mysql中也没有,则将空对象写入redis
            System.out.println("Redis缓存中没有此学生");
            student = studentMapper.findStudentById(id);
            if(student==null){
                System.out.println("Mysql中也没有此学生");
                Student s = new Student();
                s.setId(id);
                saveToRedis(s);
            }
            else{
                System.out.println("Mysql中查询到此学生");
                saveToRedis(student);
            }
            return student;
        }
    
        //    根据传入的id数据查找出一个或多个学生信息
        /**
         * @param ids:
         * @return List
         * @author GCT
         * @description 根据传入的id数据查找出一个或多个学生信息
         * @date 2022/11/12 11:30
         */
        public List<Student> findStudentByIds(Long[] ids){
            List<Student> studentList = new ArrayList<Student>();
            for (Long id:ids){
    //            遍历ids数组,使用findStudentById(id)将
    //            返回的Student类型数据添加到studentList集合中
                studentList.add(findStudentById(id));
            }
            return studentList;
        }
    
    
        //根据id修改用户信息
        @Transactional   //修改3:开启事务
        public String updateStudentById(Student student) {
            Long id = student.getId();
            if (id == null) {
                return "学生id不能为空";
            }
            //修改2. 先更新mysql数据库
            studentMapper.updateStudentById(student);
            //修改2. 后删除缓存
            String key="student:"+id;
            redisTemplate.delete(key);
            return "更新成功";
        }
    
        //保存Student信息到Redis,使用hash类型
        public void saveToRedis(Student student) {
            //设置key: student:ID
            String key="student:"+student.getId();
            //各字段的值都存入Redis
            redisTemplate.opsForHash().put(key,"sname",student.getSname()+"");
            redisTemplate.opsForHash().put(key,"dept",student.getDept()+"");
            redisTemplate.opsForHash().put(key,"age",student.getAge()); //!!! Age为Int类型不用+“”
    
            //修改 1:设置key的过期时间为6分钟
    //        redisTemplate.expire(key,360, TimeUnit.SECONDS);
    
            //缓存雪崩修改 :创建一个随机的KEY 的有效期
            int expiredTime=360+new Random().nextInt(100);
            System.out.println("过期时间: "+expiredTime);
            redisTemplate.expire(key,expiredTime, TimeUnit.SECONDS);
        }
    
        //从redis中查询Student
        public Student getStudentByRedis(Long id){
            String key="student:"+id;
            if (redisTemplate.hasKey(key)){
                String sname=(String) redisTemplate.opsForHash().get(key,"sname");
                String dept= (String) redisTemplate.opsForHash().get(key,"dept");
                int age = (Integer)redisTemplate.opsForHash().get(key,"age");
                Student student = new Student();
                student.setId(id);
                student.setSname(sname);
                student.setDept(dept);
                student.setAge(age);
                return student;
            }
            return null;
        }
    
    
    
    
        //查询用户
        public List<Student> getAllStudent() {
            return studentMapper.getAllStudentMap();
        }
    
        /**
         * @param student:
         * @return int
         * @author GCT
         * @description
         * 缓存穿透处理时对不存在的学生创建了
         * 对应id的空对象存入缓存,因此在新增学生信息时加个判断,
         * 判断新增的学生id是否存在于Redis缓存中,若存在,则删去对应缓存
         * @date 2022/11/12 11:45
         */
        @Transactional   //开启事务
        public int addStudent(Student student) {
    
            //先在mysql数据库新增数据
            int i = studentMapper.addStudent(student);
            Long studentId = student.getId();
            Student studentByRedis = getStudentByRedis(studentId);
            //后判断,若在缓存中存在对应信息则删除缓存
            if (studentByRedis!=null){
                String key="student:"+studentId;
                redisTemplate.delete(key);//若存在对应的对象,则删除缓存
            }
            System.out.println("id:  "+studentId);
    
            return i;
        }
    
        //    根据id删除学生
        /**
         * @param id:
         * @return int
         * @author GCT
         * @description 根据id删除学生
         * 使用事务
         * 先删除Mysql数据库内信息
         * 再删除redis数据库内信息
         * @date 2022/11/11 21:30
         */
        @Transactional   //开启事务
        public String deleteStudentById(Long id){
            if (id == null) {
                return "学生id不能为空!";
            }
            //先更新mysql数据库
            studentMapper.deleteStudentById(id);
            //后删除缓存
            String key="student:"+id;
            redisTemplate.delete(key);
            return "成功删除id为"+id+"的学生!";
    
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202

    com.example.controller包

    com.example.controller.StudentController

    package com.example.controller;
    
    import com.example.pojo.Student;
    import com.example.service.StudentService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.io.IOException;
    import java.util.List;
    
    /**
     * @projectName: week11_redis_x
     * @package: com.example.controller
     * @className: StudentController
     * @author: GCT
     * @description: TODO
     * @date: 2022/11/11 20:38
     * @version: 1.0
     */
    @RestController
    @RequestMapping("/student")
    public class StudentController {
    
        @Autowired
        public StudentService studentServie;
    
    
        //根据id查询学生
        @GetMapping("/getAllStudent")
        public List<Student> getAllStudent(){
            List<Student> allStudent = studentServie.getAllStudent();
            System.out.println(allStudent);
            return allStudent;
        }
    
    
        //根据id查询学生
        @GetMapping(value = "/findStudentByID")
        public Student findStudentByID(Long id){
            Student student = null;
    //        Long id = -1l;
            for(int i=0;i<10;i++) {
                student = studentServie.findStudentById(id);
                System.out.println(student);
    //            return student;
            }
            return student;
        }
    
        //根据多个id查询多个学生
        @PostMapping(value = "/findStudentByIds")
        public List<Student> findStudentByIds(@RequestBody Long[] ids){
            return studentServie.findStudentByIds(ids);
        }
    
    
    
        //修改学生信息
        @PostMapping("/updateStudentById")
        public String updateStudentById(Student student) throws IOException {
    
            String info= studentServie.updateStudentById(student);
            System.out.println(info);
            return info;
    
        }
    
    //    增加学生信息
        @PostMapping("/addStudent")
        public int addStudent(Student student){
            int res = studentServie.addStudent(student);
            System.out.println("res"+res);
            return res;
        }
    
        //    根据id删除学生
        @PostMapping("/deleteStudentById")
        public String deleteStudentById(Long id){
            String info = studentServie.deleteStudentById(id);
            System.out.println("info: "+info);
            return info;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    src/main/resources文件夹

    src/main/resources/application.yml

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/myschool?serverTimezone=Hongkong?characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 密码
      redis:
        host: 127.0.0.1
        port: 6379
        lettuce:
          pool:
            max-active: 10
            max-idle: 10
            min-idle: 0
            max-wait: 1000
    
    mybatis:
      mapper-locations: classpath:com/exmaple/mapper/*.xml    #指定sql配置文件的位置
      type-aliases-package: com.example.pojo      #指定实体类所在的包名
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   #输出SQL命令
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    pom.xml

    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.7.5version>
            <relativePath/> 
        parent>
        <groupId>com.examplegroupId>
        <artifactId>week11_redis_xxxxxxxxxxxartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>week11_redis_xxxxxxxxxxxname>
        <description>week11_redis_xxxxxxxxxxxdescription>
        <properties>
            <java.version>1.8java.version>
        properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>2.2.2version>
            dependency>
    
            <dependency>
                <groupId>com.mysqlgroupId>
                <artifactId>mysql-connector-jartifactId>
                <scope>runtimescope>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
        <build>
    
    
            
            
            
            
            
            
            
            
            
            <resources>
                <resource>
                    <directory>src/main/javadirectory>
                    <includes>
                        <include>**/*.xmlinclude>
                    includes>
                resource>
                <resource>
                    <directory>src/main/resourcesdirectory>
                    <includes>
                        <include>**.*include>
                    includes>
                resource>
            resources>
    
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombokgroupId>
                                <artifactId>lombokartifactId>
                            exclude>
                        excludes>
                    configuration>
                plugin>
            plugins>
        build>
    
    project>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    com.example.Week11RedisxxxxxxxxxxxApplication

    package com.example;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan(basePackages = "com.example.mapper") //记得加这个
    public class Week11RedisxxxxxxxxxxxApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Week11RedisxxxxxxxxxxxApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    五、数据一致性、缓存穿透、缓存雪崩实验

    1、数据一致性问题实验

    数据一致性处理:

    • 1.数据写入redis时,设置key的超时时间
    • 2.修改数据时,先修改mysql,再删除redis缓存
    • 3.开启事务:保证正确事务的提交

    根据id更新学生信息方法数据一致性实验

    首次查找(不存在于Redis,从MySQL取数据后存入Redis)

    查找前Redis中的键值对截图:
    在这里插入图片描述
    调用public Student findStudentByID(Long id)接口:
    在这里插入图片描述
    后台打印输出:
    在这里插入图片描述
    此时Redis数据库中键值对:
    在这里插入图片描述

    调用根据id更新学生信息接口public String updateStudentById(Student student):
    在这里插入图片描述
    后台打印信息:
    在这里插入图片描述
    此时查看Redis数据库可以看到更新了的对应学生信息已被删除:
    在这里插入图片描述
    再次调用public Student findStudentByID(Long id)接口查找对应学生信息:
    在这里插入图片描述
    后台打印信息:

    在这里插入图片描述
    此时查看Redis数据库可以看到更新后的数据成功缓存:
    在这里插入图片描述
    可以看到数据成功更新且保持了数据的一致性。

    根据id删除学生信息方法数据一致性实验结果:

    调用public Student findStudentById(Long id)查找id为61的学生:
    调用前Mysql数据库对应信息:
    在这里插入图片描述
    调用前Redis数据库键值对信息:
    在这里插入图片描述

    调用该接口:
    在这里插入图片描述
    后台打印输出:
    在这里插入图片描述
    此时该学生信息已存入Redis中进行缓存:
    在这里插入图片描述
    此时调用public String deleteStudentById(Long id)接口删除id为61的学生:
    在这里插入图片描述后台打印输出:
    在这里插入图片描述
    此时查看MySQL数据库可以看到数据已成功删除:
    在这里插入图片描述
    再查看Redis数据库:
    在这里插入图片描述
    可以看到对应的缓存信息也已经被删除,由此可以看到成功实现了数据的一致性。

    新增学生信息方法数据一致性实验

    由于缓存穿透处理时对不存在的学生创建了对应id的空对象存入缓存,因此在新增学生信息时加个判断,判断新增的学生id是否存在于Redis缓存中,若存在,则删去对应缓存

    id为109的学生暂不存在于数据库中:
    在这里插入图片描述
    此时Redis数据库中也无对应的信息:
    在这里插入图片描述
    调用public List findStudentByIds(@RequestBody Long[] ids)接口查找id为109的学生信息

    由于缓存穿透问题处理,程序会将id为109的空对象写入redis进行缓存
    在这里插入图片描述
    后台打印输出:
    在这里插入图片描述
    此时查看数据库可以看到id为109的空对象已存入数据库中缓存
    在这里插入图片描述
    此时调用public int addStudent(Student student)接口新增id为109的学生信息(id自动递增,现在Mysql数据库中末尾id为108,插入新数据后新数据id为109)
    在这里插入图片描述
    后台打印信息:
    在这里插入图片描述
    此时查看Redis数据库可以看到id为109的缓存信息已被删除:

    在这里插入图片描述
    此时调用public Student findStudentByID(Long id)查询id为109的学生信息:
    在这里插入图片描述
    后台打印输出:
    在这里插入图片描述
    可以看到成功查找出新增的学生信息。

    查看Redis数据库可以看到新增的学生信息也成功加入缓存:
    在这里插入图片描述
    由此可以看到成功实现了数据的一致性。

    2. 缓存穿透问题处理实验

    查找的数据如果在mysql中也没有,则将空对象写入redis进行缓存

    缓存穿透问题处理实验:
    经过删除id为61的学生信息操作后可知此时Mysql数据库与Redis数据库中均无id为61的学生的信息:
    在这里插入图片描述
    在这里插入图片描述此时调用public Student findStudentById(Long id)接口查找不存在于MySQL与Redis中,id为61的学生信息:
    在这里插入图片描述
    后台打印信息:

    在这里插入图片描述
    可以看到当Mysql数据库与Redis数据库中均找不到该学生信息时,程序创建了一个id为61的空对象写
    入redis进行缓存,以此来解决缓存穿透问题。

    3. 缓存雪崩处理实验

    为存入Redis数据库进行缓存的键值对创建一个随机的Key的有效期

    缓存雪崩处理实验:
    编写public List findStudentByIds(@RequestBody Long[] ids)接口及对应的service层,mapper层代码用于批量查找学生信息

    调用public List findStudentByIds(@RequestBody Long[] ids)接口批量查找id为1,2,3,4,5的学生信息并观察各个键值对的有效期:
    在这里插入图片描述
    后台打印信息:
    在这里插入图片描述
    可以看到Redis数据库中各个键值对过期时间均为随机产生,以此来解决缓存雪崩问题。

  • 相关阅读:
    化云为雨,华为云为什么要深入经济的“毛细血管”?
    13000 行代码、19 大技术,这位 16 岁高中生用 C++ 从头到尾构建了一个机器学习库
    学好大数据能做什么工作?
    Frida
    Mysql InnoDB Redo log
    《Deep Residual Learning for Image Recognition》阅读笔记
    Docker Compose教程
    一口气拿下vue-router所有知识点,薪资暴涨3000
    labuladong算法小抄-数据结构设计-leetcode146、leetcode341、leetcode380、leetcode460
    drone如何发布docker服务
  • 原文地址:https://blog.csdn.net/GCTTTTTT/article/details/127818140