缓存穿透
缓存雪崩
缓存击穿
加锁解决缓存击穿
单体应用,本地锁
锁的时序问题
应该避免前一个线程释放锁后还没将结果放进缓存其他线程就拿到锁并进行查库,保证数据库查到
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//给缓存中放json字符串,拿出的字符串还能逆转成能用的对象类型【序列化和反序列化】
//加入缓存逻辑,缓存中存的是json字符串
//json跨语言跨平台
ValueOperations<String, String> ops = redisTemplate.opsForValue();
String catalogJson = ops.get("catalogJson");
if (catalogJson == null) {
//缓存中没有,查询数据库
Map<String, List<Catelog2Vo>> categoriesObjDb = getCatalogJsonFromDB();
return categoriesObjDb;
}
//转为指定的对象
Map<String, List<Catelog2Vo>> catalogJsonObj = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
return catalogJsonObj;
}
//从数据库查询并封装分类数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {
synchronized (this){
//将数据库的多次查询变为一次
List<CategoryEntity> selectList = baseMapper.selectList(null);
// 查询所有一级分类
List<CategoryEntity> level1 = getParent_cid(selectList,0L);
Map<String, List<Catelog2Vo>> catalogJson = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
// 拿到每一个一级分类 然后查询他们的二级分类
List<CategoryEntity> level2 = getParent_cid(selectList,v.getCatId());
List<Catelog2Vo> catelog2Vos = null;
if (level2 != null) {
catelog2Vos = level2.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(),null , l2.getCatId().toString(), l2.getName());
// 找当前二级分类的三级分类
List<CategoryEntity> level3 = getParent_cid(selectList,l2.getCatId());
// 三级分类有数据的情况下
if (level3 != null) {
List<Catelog2Vo.Catelog3Vo> catelog3Vos = level3.stream().map((l3) -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(),l3.getCatId().toString(),l3.getName());
return catelog3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(catelog3Vos);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
//将数据库中查到的数据,转为json放入缓存
String s = JSON.toJSONString(catelog2Vos);
redisTemplate.opsForValue().set("catalogJson",s,1, TimeUnit.DAYS);
return catelog2Vos;
}));
return catalogJson;
}
}
分布式锁
占锁成功
占锁失败
//从数据库查询并封装分类数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1.占分布式锁(去redis占坑)
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1");
if (lock){
//加锁成功...执行查db的操作
Map<String, List<Catelog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
//解锁
redisTemplate.delete("lock");
return catalogJsonFromDB;
}else {
//加锁失败...休眠100ms重试 (自旋锁)
return getCatalogJsonFromDBWithRedisLock();
}
}
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//给缓存中放json字符串,拿出的字符串还能逆转成能用的对象类型【序列化和反序列化】
//加入缓存逻辑,缓存中存的是json字符串
//json跨语言跨平台
ValueOperations<String, String> ops = redisTemplate.opsForValue();
String catalogJson = ops.get("catalogJson");
if (catalogJson == null) {
//缓存中没有,查询数据库
Map<String, List<Catelog2Vo>> categoriesObjDb = getCatalogJsonFromDBWithRedisLock();
return categoriesObjDb;
}
//转为指定的对象
Map<String, List<Catelog2Vo>> catalogJsonObj = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return catalogJsonObj;
}
//从数据库查询并封装分类数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1.占分布式锁(去redis占坑)
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catelog2Vo>> catalogJsonFromDB;
try {
catalogJsonFromDB = getCatalogJsonFromDB();
} finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long l = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
return catalogJsonFromDB;
} else {
//加锁失败...休眠100ms重试 (自旋锁)
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDBWithRedisLock();
}
}
两个核心:
- 加锁时设置过期时间,是原子操作
- 获取uuid并删锁,使用lua脚本,是原子操作