(1)缓存雪崩
问题分析:redis的大量数据出现同时过期,大量请求同时访问mysql,导致mysql无法处理而崩溃
解决方法:设置不同的缓存失效时间,原有的失效时间会增加一个随机值,每个缓存的过期时间重复率就会降低,就很难出现失效的问题。
添加一个失效时间
这里设置的是50,后面的TimeUnit,SECONDS是时间单位秒
//ops.set(PREFIX+id,car1,50, TimeUnit.SECONDS);
(2)缓存击穿
问题分析:一些数据还没保存到redis,就被超高并发访问,在这之前有大量的请求进行访问,会导致压力过大所产生的
解决方法:可以通过上双检锁的方法实现线程同步执行
双检锁解决击穿问题
@Service
public class CarServiceImpl extends ServiceImpl<CarMapper, Car> implements CarService {
@Autowired
private CarMapper carMapper;
public static final String PREFIX="Car-";
@Autowired
private RedisTemplate<String,Object> redisTemplate;
//设置同步锁验证
@Override
public Car getCarById(Long id) {
//获取字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
//先查询Redis
Car car = (Car)ops.get(PREFIX + id);
if (car == null){
synchronized (this){
System.out.println("进入同步锁");
//先查询Redis
car = (Car)ops.get(PREFIX + id);
//如果redis是存在的,就会直接返回
if (car!=null){
System.out.println("redis查到了,返回"+car);
return car;
}
//如果redis没有查到数据,就查mysql
car = carMapper.selectById(id);
//mysql查到数据,保存到redis
if (car!=null){
System.out.println("mysql查到,返"+car);
ops.set(PREFIX+id,car);
return car;
}else {
//mysql没有数据
System.out.println("mysql没数据");
Car car1 = new Car();
//设置存在的期限
ops.set(PREFIX+id,car1,50, TimeUnit.SECONDS);
}
// mysql没有数据,返回null
System.out.println("mysql没数据,返回null");
}
}else {
System.out.println("没有执行同步锁");
}
return car;
}
(3)缓存穿透
问题分析:查询一个不存在的数据,导致缓存不被命中,就回去查数据库,但是也没有数据,也没有将这次查询的null写入缓存,导致这个不存在的数据被多次访问查询,失去缓存的意义,存在一定的风险。
解决方法:在redis中保存空的对象,然后给空对象设置过期时间、布隆过滤器
布隆过滤器
定义:它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
(1)引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.0</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("car-filter");
//初始化 参数1 向量长度 参数2 误识别率
bloomFilter.tryInit(10000000L,0.03);
return bloomFilter;
}
}
(3)初始化布隆过滤器
原因:布隆过滤器在使用之前需要进行初始化的原因是为了确定布隆过滤器的大小(位数组的大小)和哈希函数的数量
@GetMapping("/init-car-filter")
public ResponseResult<String> initCarFilter(){
List<Car> list = carService.list();
//将所有的id保存到过滤器中
list.forEach(car -> {
rBloomFilter.add(String.valueOf(car.getId()));
});
return ResponseResult.ok("ok");
}
(4)使用布隆过滤器排除为空的数据
@Override
public Car getCarById(Long id) {
//获取字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
//先查询Redis
Car car = (Car)ops.get(PREFIX + id);
if (car == null){
synchronized (this){
System.out.println("进入同步锁");
//先查询Redis
car = (Car)ops.get(PREFIX + id);
//如果redis是存在的,就会直接返回
if (car!=null){
System.out.println("redis查到了,返回"+car);
return car;
}
//使用布隆过滤器判断数据库是否存在该id
if(rBloomFilter.contains(String.valueOf(id))){
//如果redis没有查到数据,就查mysql
car = carMapper.selectById(id);
//mysql查到数据,保存到redis
if (car!=null){
System.out.println("mysql查到,返"+car);
ops.set(PREFIX+id,car);
return car;
}
}else {
System.out.println("布隆过滤器判断id数据库不存在,直接返回");
}
}
}else {
System.out.println("没有执行同步锁");
}
return car;
}
总之,要处理 Redis 的并发问题,需要注意原子性操作、缓存失效处理、分布式锁、排他性访问以及阻塞操作和超时处理等方面。选择适当的解决方案取决于具体的业务场景和要解决的问题。