击穿
Redis曾存在的key,由于过期时间而被删除,导致请求跳过redis而访问DB
处理方法:
- 不设置过期时间,永远存在
- 使用锁,synchronized、分布式锁
- 布隆过滤器
穿透
数据库与redis都不存在的key,由于莫名原因存在大量请求,导致请求跳过redis而访问DB
处理方法:
- 数据库不存在,redis也存储一个 null(或者常熟),防止跳过redis
雪崩
多个不同的key,由于设置了相同的过期时间,导致多个key在同一时间而被删除,从而导致DB压力过大
处理方法:
- 对每个key的过期时间,通过
RandomUtils.nextInt(10)+1
创建一个随机时间
代码功能实现:穿透、击穿、雪崩
/**
* 使用 redis 进行缓存
* @return
*/
@Override
public Map<String, List<Catalog2VO>> getCatelog2JSON() {
// 先查询redis,是否有数据存储、
// 如果有拿去返回,如果没有进行存储
System.out.println("属于双重判断的 第一个判断");
String catelogs = stringRedisTemplate.opsForValue().get(CATEGORY_KEYS);
// 等于 0 说明数据库暂时没有数据
if (Objects.equals(catelogs,"0")) {
return null;
}
// 等于 null,说明没有查询过数据库
if (Strings.isNullOrEmpty(catelogs)){
Map<String, List<Catalog2VO>> catelog2JSONForDB = null;
System.out.println("添加锁功能,是为了防止缓存击穿");
// 单体:synchronized (this) {...}
// 分布式 stringRedisTemplate.opsForValue().setIfAbsent(...) 如果不存在则进行添加
String uuid = UUID.randomUUID().toString();
// 防止查询数据库存在问报错,而导致没有解锁的问题。所以加入超时时间
if(stringRedisTemplate.opsForValue().setIfAbsent("catelogLock", uuid,30, TimeUnit.SECONDS)) {
try{
catelog2JSONForDB = getCatelog2JSONForDB();
return catelog2JSONForDB;
} finally {
System.out.println("解锁(原子性)");
String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end ";
// 通过Redis的lua脚本实现 查询和删除操作的原子性
stringRedisTemplate.execute(new DefaultRedisScript<Long>(srcipts,Long.class)
,Arrays.asList("lock"),uuid);
}
} else {
// 睡眠一段时间,然后重新获取内容
// 就是循环调用
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return getCatelog2JSON();
}
}
// 获取到数据,并返回
Map<String, List<Catalog2VO>> map = JSONObject.parseObject(catelogs, new TypeReference<Map<String, List<Catalog2VO>>>() {
});
return map;
}
/**
* 查询出所有的二级和三级分类的数据
* 并封装为Map<String, Catalog2VO>对象
*
* 进行优化
* @return
*/
private Map<String,List<Catalog2VO>> getCatelog2JSONForDB() {
System.out.println("属于双重判断的 第二个判断");
String catelogJson = stringRedisTemplate.opsForValue().get(CATEGORY_KEYS);
if ("0".equals(catelogJson)) { // 查询报错结果
return null;
} else if (!Strings.isNullOrEmpty(catelogJson)) { // 查询正常结果
Map<String, List<Catalog2VO>> map = JSONObject.parseObject(catelogJson, new TypeReference<Map<String, List<Catalog2VO>>>() {
});
return map;
}
System.out.println("开始查询数据库------------->");
// 获取所有的分类的数据
List<CategoryEntity> list = this.list();
// 获取一级分类
List<CategoryEntity> leve1Category = queryByParenCid(list, 0l);
// 把一级分类的数据转换为Map容器 key就是一级分类的编号, value就是一级分类对应的二级分类的数据
Map<String, List<Catalog2VO>> map = leve1Category.stream().collect(Collectors.toMap(
key -> key.getCatId().toString()
, value -> {
// 省略具体业务
return null;
}
));
System.out.println("防止缓存穿透与雪崩------------->");
if (map == null || map.size() == 0) {
// 1.添加较短时间线,是为了防止缓存穿透
stringRedisTemplate.opsForValue().set(CATEGORY_KEYS, "0", 5, TimeUnit.SECONDS);
} else {
// 随机数的过期时间,是为了防止缓存雪崩
stringRedisTemplate.opsForValue().set(CATEGORY_KEYS, JSONObject.toJSONString(map), RandomUtils.nextInt(10)+1, TimeUnit.HOURS);
}
return map;
}
标签:map,缓存,return,Map,Redis,redis,key,null,分布式
From: https://www.cnblogs.com/zz-1q/p/18227312