- 并发:多个线程同时操作某一个(些)资源,带来数据的不确定性、不稳定性、不安全性
- 同步:在某一个时刻,只有一个线程访问资源 解决并发问题,性能低下(程序不能让性能过于低下)
- 锁:唯一 对象监视器
- 缓存穿(刺)透:缓存有(没有)数据,访问了数据库
- 缓存雪崩:在某一个时刻,缓存中大部分 同时失效,而此时恰好有很多线程并发访问,导致数据库无法处理这么多访问而瘫痪
- 评估 缓存数据分时失效 降格处理
- 多线程:不是计算一个数据 而是描述的是一种状态
完善根据学生id查询学生的功能,先从redis缓存中查找,如果找不到,再从数据库中查找,然后放到redis缓存中
首先通过MyBatis逆向工程生成实体bean和数据持久层
- <!-- 加载spring boot redis包 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>2.0.0</version>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- <build>
- <resources>
- <resource>
- <directory>src/main/java</directory>
- <includes>
- <include>**/*.xml</include>
- </includes>
- </resource>
- </resources>
-
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- server.port=9005
- server.servlet.context-path=/005-springboot-redis
- #数据库的配置
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
- spring.datasource.username=root
- spring.datasource.password=root
- #redis配置
- spring.redis.host=192.168.60.130
- spring.redis.port=6379
- spring.redis.password=123456
01-Centos7安装Redis和启动Redishttps://blog.csdn.net/qq_45037155/article/details/124679838
- [root@Suke /]# cd /usr/local/redis-4.0.6/src/
- [root@Suke src]# ./redis-server ../redis.conf &
- @RestController
- public class RedisController {
-
- @Autowired
- private StudentService studentService;
-
- /*
- * http://localhost:9005/005-springboot-redis/springboot/allStudentCount
- * */
- @GetMapping(value = "/springboot/allStudentCount")
- public Object allStudentCount(HttpServletRequest request) {
-
- Long allStudentCount = studentService.queryAllStudentCount();
-
- return "学生总人数:" + allStudentCount;
-
- }
- }
- public interface StudentService {
-
- Long queryAllStudentCount();
-
- }
配置了上面的步骤,Spring Boot将自动配置RedisTemplate,在需要操作redis的类中注入redisTemplate即可。
注意:Spring Boot帮我们注入RedisTemplate类,泛型里面只能写 <String, String>、<Object, Object>或者什么都不写
- @Service
- public class StudentServiceImpl implements StudentService {
-
- @Autowired
- StudentMapper studentMapper;
-
- @Autowired
- RedisTemplate redisTemplate;
-
- @Override
- public Long queryAllStudentCount() {
- //从redis缓存中获取总人数
- Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
- //判断是否为空
- if (allStudentCount == null) {
- //从数据库查询
- allStudentCount = studentMapper.selectAllStudentCount();
- //将值再存放到redis缓存中
- redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
- }
- return allStudentCount;
- }
- }
- public class Student {
- private Integer id;
-
- private String name;
-
- private Integer age;
-
- public Integer getId() {
- return id;
- }
-
- public void setId(Integer id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public Integer getAge() {
- return age;
- }
-
- public void setAge(Integer age) {
- this.age = age;
- }
- }
- @Repository
- public interface StudentMapper {
-
- Long selectAllStudentCount();
- }
- <?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.suke.springboot.mapper.StudentMapper">
- <resultMap id="BaseResultMap" type="com.suke.springboot.model.Student">
- <id column="id" jdbcType="INTEGER" property="id"/>
- <result column="name" jdbcType="VARCHAR" property="name"/>
- <result column="age" jdbcType="INTEGER" property="age"/>
- </resultMap>
- <sql id="Base_Column_List">
- id
- , name, age
- </sql>
-
- <select id="selectAllStudentCount" resultType="long">
- select count(123)
- from tb_student
- </select>
- </mapper>
在SpringBoot启动类上添加扫描数据持久层的注解并指定扫描包
- @SpringBootApplication
- @MapperScan("com.suke.springboot.mapper")//扫描数据持久层
- public class Application {
-
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-
- }
设置20秒后,缓存消失
关系型数据库:数据安全的
redis:二进制数据安全的 数据直接序列化 存储到redis中,获取的时候,直接把序列化数据拿出来,反序列化成对象 不会产生乱码的
- //设置key键,序列化,与功能无关
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- @Service
- public class StudentServiceImpl implements StudentService {
-
- @Autowired
- StudentMapper studentMapper;
-
- @Autowired
- RedisTemplate redisTemplate;
-
- @Override
- public Long queryAllStudentCount() {
- //设置key键,序列化,与功能无关
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- //从redis缓存中获取总人数
- Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
- //判断是否为空
- if (allStudentCount == null) {
- //从数据库查询
- allStudentCount = studentMapper.selectAllStudentCount();
- //将值再存放到redis缓存中
- redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
- }
- return allStudentCount;
- }
- }
创建线程池模拟千人并发
- @RestController
- public class RedisController {
-
- @Autowired
- private StudentService studentService;
-
- /*
- * http://localhost:9005/005-springboot-redis/springboot/allStudentCount
- * */
- @GetMapping(value = "/springboot/allStudentCount")
- public Object allStudentCount(HttpServletRequest request) {
-
- /*Long allStudentCount = studentService.queryAllStudentCount();
- return "学生总人数:" + allStudentCount;*/
-
- //创建线程池
- ExecutorService executorService = Executors.newFixedThreadPool(16);
- //模拟千人并发
- for (int i = 0; i < 1000; i++) {
- executorService.submit(new Runnable() {
- @Override
- public void run() {
- studentService.queryAllStudentCount();
- }
- });
- }
- return "学生总人数:" + 7;
- }
- }
在控制台输出”查询数据库“和“缓存命中”显示查询结果情况
- @Service
- public class StudentServiceImpl implements StudentService {
-
- @Autowired
- StudentMapper studentMapper;
-
- @Autowired
- RedisTemplate redisTemplate;
-
- @Override
- public Long queryAllStudentCount() {
- //设置key键,序列化,与功能无关
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- //从redis缓存中获取总人数
- Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
- //判断是否为空
- if (allStudentCount == null) {
- System.out.println("----查询数据库-----");
- //从数据库查询
- allStudentCount = studentMapper.selectAllStudentCount();
- //将值再存放到redis缓存中
- redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
- } else {
- System.out.println("-----缓存命中-------");
- }
- return allStudentCount;
- }
- }
Tip:多个线程都去查询数据库,这种现象就叫做缓存穿透,如果并发比较大,对数据库的压力过大,有可能造成数据库宕机。
- @Service
- public class StudentServiceImpl implements StudentService {
-
- @Autowired
- StudentMapper studentMapper;
-
- @Autowired
- RedisTemplate redisTemplate;
-
- @Override
- public Long queryAllStudentCount() {
- //设置key键,序列化,与功能无关
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- //从redis缓存中获取总人数
- Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
- //判断学生总人数是否为空
- if (allStudentCount == null) {
- //设置同步代码块
- synchronized (this) {
- //再次从redis缓存中获取学生总人数
- allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
- //双重检测判断缓存中是否有数据
- if (allStudentCount == null) {
- System.out.println("----查询数据库-----");
- //从数据库查询
- allStudentCount = studentMapper.selectAllStudentCount();
- //将值再存放到redis缓存中
- redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 20, TimeUnit.SECONDS);
- } else {
- System.out.println("-----缓存命中-------");
- }
- }
- } else {
- System.out.println("-----缓存命中-------");
- }
- return allStudentCount;
- }
- }
只有第一个线程查询数据库,其它线程查询Redis缓存,这样的解决的小问题就是第一批进来的用户会有一个等待,但是这样的影响可以忽略
- 防止线程获取到cpu执行权限的时候,其他线程已经将数据放到Redis中了,所以再次判断
- 不能将synchronized范围扩大,因为如果Redis缓存中如果有数据,线程不应该同步,否则影响效率
- 解决缓存穿透:2次查询缓存,再一次判断