一、Redis缓存作用
Redis在项目中常用作缓存来使用,主要用两大作用:
1.提升系统的性能
Redis基于内存,IO效率远高于MySql数据库
2.减少数据库压力
Redis处理很多请求,使用Redis作为缓存可以减少数据库的请求量,避免数据库因为请求过多、压力过大而导致宕机。
二、Redis的使用流程
1.查询数据
2.先查询Redis,判断是否存在。
3.如果存在就返回数据,如果不存在就查询MySql数据库
4.查询数据库是否存在,如果存在就保存在Redis中,如果存在就返回空的对象给数据。
Redis使用流程图
三、Redis并发问题介绍
并发问题是大量并发量访问服务器可能导致的问题。
问题 | 原因 | 解决方案 |
雪崩 | 1.Redis热点数据同时过期,大量请求全部打在MySql上,mysql宕机。 2.单个Redis服务出现问题或者重启 | 1.将热点数据过期时间设为随机值,避免同时过期 2.配置Redis集群,结局单点故障的问题 |
击穿 | 大量并发请求访问Redis同一个数据,还没有向Redis保存,有大量数据同时访问,导致MySql压力很大 | 通过上锁(双检锁)实现线程同步执行 |
穿透 | 大量请求访问MySQL没有的数据,Redis缓存无法命中,导致数据库压力过大 | 1. 在Redis中保存空对象,给空对象设置过期时间 2. 使用布隆过滤器筛选掉不存在的数据 |
击穿问题解决案例
解决方法:使用双检锁DCL机制优化方法,通过给代码块加上synchronized锁,同步代码块的方式。再加上if else判断的方式来优化方法,解决击穿的问题,优化性能。
@Override
public Student getStudentById(Long id) {
//获得字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
//先查询Redis
Student stu = (Student) ops.get(PREFIX + id);
if(stu == null) {
synchronized (this) {
System.out.println("进入了同步锁");
//先查询Redis
stu = (Student) ops.get(PREFIX + id);
//如果Redis缓存存在数据,就直接返回
if (stu != null) {
System.out.println("Redis查到,返回" + stu);
return stu;
}
//如果Redis没有查到数据,就查询MySQL
stu = studentMapper.selectById(id);
//MySQL查到数据,就保存到Redis
if (stu != null) {
System.out.println("MySQL查到,返回" + stu);
ops.set(PREFIX + id, stu);
return stu;
}
//MySQL没有数据,就返回null
System.out.println("MySQL没有数据,就返回null");
}
}else {
System.out.println("没有执行同步锁");
}
return stu;
}
穿透问题
解决方案1 : 保存空对象设置过期时间
@Override
public Student getStudentById(Long id) {
//获得字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
//先查询Redis,如果存在数据就不执行同步代码块,直接返回
Student stu = (Student) ops.get(PREFIX + id);
if(stu == null) {
synchronized (this) {
System.out.println("进入了同步锁");
//先查询Redis
stu = (Student) ops.get(PREFIX + id);
//如果Redis缓存存在数据,就直接返回
if (stu != null) {
System.out.println("Redis查到,返回" + stu);
return stu;
}
//如果Redis没有查到数据,就查询MySQL
stu = studentMapper.selectById(id);
//MySQL查到数据,就保存到Redis
if (stu != null) {
System.out.println("MySQL查到,返回" + stu);
ops.set(PREFIX + id, stu);
return stu;
}else {
//MySQL没有数据,在Redis保存空对象,设置过期时间
System.out.println("MySQL没有数据");
Student student = new Student();
ops.set(PREFIX + id, student,5, TimeUnit.SECONDS);
}
}
}else {
System.out.println("没有执行同步锁");
}
return stu;
}
解决方法2:使用布隆过滤器。
布隆过滤器:
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
判断不存在的数据一定不存在,判断存在的数据可能不存在。
使用布隆过滤器:
1) 将数据保存到布隆过滤器中
2) 使用布隆过滤器进行判断
1.先引入redisson依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.6</version>
</dependency>
2.创建布隆过滤器
编写配置类
@Configuration
public class RedissonConfig {
@Bean
public RBloomFilter<String> bloomFilter(){
Config config = new Config();
config.setTransportMode(TransportMode.NIO);
SingleServerConfig singleServerConfig = config.useSingleServer();
//可以用"rediss://"来启用SSL连接
singleServerConfig.setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
//创建布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("student-filter");
//初始化 参数1 向量长度 参数2 误识别率
bloomFilter.tryInit(10000000L,0.03);
return bloomFilter;
}
}
3.创建集合发送请求,将数据ID保存在过滤器中
@Autowired
private RBloomFilter<String> rBloomFilter;
/**
* 初始化布隆过滤器
* @return
*/
@GetMapping("init-student-filter")
public ResponseResult<String> initStudentFilter(){
List<Student> list = studentService.list();
//将所有id保存到过滤器中
list.forEach(student -> {
rBloomFilter.add(String.valueOf(student.getStuId()));
});
return ResponseResult.ok("ok");
}
4.使用过滤器排除不存在的数据
@Autowired
private RBloomFilter<String> rBloomFilter;
@Override
public Student getStudentById(Long id) {
//获得字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
//先查询Redis
Student stu = (Student) ops.get(PREFIX + id);
if(stu == null) {
synchronized (this) {
System.out.println("进入了同步锁");
//先查询Redis
stu = (Student) ops.get(PREFIX + id);
//如果Redis缓存存在数据,就直接返回
if (stu != null) {
System.out.println("Redis查到,返回" + stu);
return stu;
}
//使用布隆过滤器判断数据库中是否存在该id
if(rBloomFilter.contains(String.valueOf(id))) {
//如果Redis没有查到数据,就查询MySQL
stu = studentMapper.selectById(id);
//MySQL查到数据,就保存到Redis
if (stu != null) {
System.out.println("MySQL查到,返回" + stu);
ops.set(PREFIX + id, stu);
return stu;
}
}else{
System.out.println("布隆过滤器判断id数据库不存在,直接返回");
}
}
}else {
System.out.println("没有执行同步锁");
}
return stu;
}