SpringBoot 集成Redis单机模式
项目名称:016-springboot-redis
完善根据学生id查询学生的功能,先从redis缓存中查找,如果找不到,再从数据库中查找,然后放到redis缓存中。
首先通过MyBatis逆向工程生成实体bean和数据持久层。
① 在pom.xml文件中添加redis依赖
<!-- 加载spring boot redis包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
② 在Spring Boot核心配置文件application.properties中配置redis连接信息
完整application.properties配置文件如下:
#配置内嵌Tomcat端口号
server.port=9090
#配置项目上下文根
server.servlet.context-path=/016-springboot-redis
#配置连接MySQL数据库信息
spring.datasource.url=jdbc:mysql://192.168.92.134:3306/springboot?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#配置视图解析器
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
#配置redis连接信息(单机模式)
spring.redis.host=192.168.92.134
spring.redis.port=6379
spring.redis.password=123456
③ 启动redis服务
④ RedisController类
RestController
public class RedisController {
@Autowired
private StudentService studentService;
/**
* 请求地址:http://localhost:9090/016-springboot-redis//springboot/allStudentCount
* @param request
* @return
*/
@GetMapping(value = "/springboot/allStudentCount")
public Object allStudentCount(HttpServletRequest request) {
Long allStudentCount = studentService.queryAllStudentCount();
return "学生总人数:" + allStudentCount;
}
}
⑤ StudentService接口
public interface StudentService {
/**
* 获取学生总人数
* @return
*/
Long queryAllStudentCount();
}
⑥ 在StudentServiceImpl中注入RedisTemplate并修改根据id获取学生的方法配置了上面的步骤,Spring Boot将自动配置RedisTemplate,在需要操作redis的类中注入redisTemplate即可。
注意:Spring Boot帮我们注入RedisTemplate类,泛型里面只能写 、或者什么都不写。
@Service("studentServiceImpl")
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Long queryAllStudentCount() {
//设置redisTemplate对象key的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
//从redis缓存中获取总人数
Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
//判断是否为空
if (null == allStudentCount) {
//去数据库查询,并存放到redis缓存中
allStudentCount = studentMapper.selectAllStudentCount();
redisTemplate.opsForValue().set("allStudentCount",allStudentCount,15, TimeUnit.SECONDS);
}
return allStudentCount;
}
}
⑦ 启动类Application在SpringBoot启动类上添加扫描数据持久层的注解并指定扫描包
@SpringBootApplication
@MapperScan(basePackages = "com.sxbdqn.springboot.mapper")//扫描数据持久层
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
⑧ 让Student类实现序列化接口(可选)在类名上Alt + 回车,如果没有提示生成序列化id,那么需要做如下的配置
⑨ 启动SpringBoot应用,访问测试
⑩ 打开Redis Desktop Mananger查看Redis中的情况
项目名称:017-springboot-redis-synchronized
首先通过MyBatis逆向工程生成实体bean和数据持久层。
1.pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sxbdqn.springboot</groupId>
<artifactId>017-springboot-redis-synchronized</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>017-springboot-redis-synchronized</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--SpringBoot web项目起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--MyBatis集成SpringBoot框架的起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--连接MySQL的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 加载spring boot redis包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<!--mybatis代码自动生成插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<!--配置文件的位置-->
<configurationFile>GeneratorMapper.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.核心配置文件application.properties
#配置内嵌Tomcat端口号
server.port=9090
#配置项目上下文根
server.servlet.context-path=/017-springboot-redis-synchronized
#配置MySQL数据库连接
spring.datasource.url=jdbc:mysql://192.168.92.134:3306/springboot?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#配置redis缓存
spring.redis.host=192.168.92.134
spring.redis.port=6379
spring.redis.password=123456
#配置SpringMVC视图解析器
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
3.Dao数据持久层
4.Service业务层
@Service("studentServiceImpl")
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Override
public Long queryAllStudentCount() {
//设置redis的key序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
//从redis中获取学生总人数
Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
//判断是否为空
if (null == allStudentCount) {
System.out.println("查询数据库。。。。。");
//从数据库查询
allStudentCount = studentMapper.selectAllStudentCount();
//将值再存放到redis缓存中
redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 15, TimeUnit.HOURS);
} else {
System.out.println("查找Redis。。。。。");
}
return allStudentCount;
}
}
5.Controller层
@RestController
public class RedisController {
@Autowired
private StudentService studentService;
@GetMapping(value = "/springBoot/student")
public @ResponseBody Object student() {
//线程池个数,一般建议是CPU内核数 或者 CPU内核数据*2
ExecutorService executorService = Executors.newFixedThreadPool(8);
for (int i = 0; i < 1000; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
studentService.queryAllStudentCount();
}
});
}
return "学生总人数:" + studentService.queryAllStudentCount();
}
}
6.启动类
@SpringBootApplication
@MapperScan(basePackages = "com.sxbdqn.springboot.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
7.启动应用程序,浏览器访问测试
8.造成的问题
Tip:多个线程都去查询数据库,这种现象就叫做缓存穿透,如果并发比较大,对数据库的压力过大,有可能造成数据库宕机。
项目名称:018-springboot-redis-synchronized
项目描述:018-springboot-redis-synchronized项目是在017-springboot-redis-synchronized项目基础上进行解决缓存穿透现象
1. 修改StudentServiceImpl中的代码
@Service("studentServiceImpl")
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Override
public Long queryAllStudentCount() {
//设置redisTemplate对象的key的序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
//从redis缓存中获取学生总人数
Long allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
//判断学生总人数是否为空
if (null == allStudentCount) {
//设置同步代码块
synchronized (this) {
//再次从redis缓存中获取学生总人数
allStudentCount = (Long) redisTemplate.opsForValue().get("allStudentCount");
//双重检测判断缓存中是否有数据
if (null == allStudentCount) {
System.out.println("从数据库获取数据");
//从数据库获取学生总人数
allStudentCount = studentMapper.selectAllStudentCount();
//将此值存放到redis缓存中
redisTemplate.opsForValue().set("allStudentCount", allStudentCount, 15, TimeUnit.MINUTES);
} else {
System.out.println("从Redis中获取数据。。。。");
}
}
} else {
System.out.println("从Redis中获取数据。。。。");
}
return allStudentCount;
}
}
2. 启动应用程序,浏览器访问测试,查看控制台输出只有第一个线程查询数据库,其它线程查询Redis缓存,这样的解决的小问题就是第一批进来的用户会有一个等待,但是这样的影响可以忽略。
3. springboot集成Redis阻止缓存穿透,为什么要做双层验证
防止线程获取到cpu执行权限的时候,其他线程已经将数据放到Redis中了,所以再次判断;
不能将synchronized范围扩大,因为如果Redis缓存中如果有数据,线程不应该同步,否则影响效率。
1. 在pom.xml中配置相关的jar依赖
这步我们前面已经做过
<!-- 加载spring boot redis包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 在Springboot核心配置文件application.properties中配置redis连接信息
IP地址及master根据自己的情况进行修改
#哨兵模式redis集群配置(哨兵模式)
spring.redis.password=123456
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=192.168.235.128:26380,192.168.235.128:26382,192.168.235.128:26384
3. 通过rz –y命令,用我提供的资源中的配置文件覆盖Linux服务器上Redis的配置文件,注意根据自己机器的情况修改资源文件ip及引用的redis路径
我们目前6384是主,6380和6382是从
4. 启动三台Redis服务器
5. 在Xshell中开三个窗口,启动三台Redis哨兵
6. 在Redis Desktop Manager上创建三个连接,连接6380,6382,6384Redis服务器
7. 清空6384主数据,查看情况
6380和6382也跟着清空。
8. 启动SpringBoot主程序,浏览器访问,查看Redis情况
Redis6380、6382、6384中都会存入数据。
9. 将Redis6384主停止再启动
哨兵会将Redis6382作为主,等Redis6384再次启动后,会作为Redis6382的从。