redis 的问题
在使用redis作为缓存中间件时,在高并发的情况下,会出现缓存失效的问题。
缓存穿透、缓存雪崩、缓存击穿。
缓存穿透
缓存雪崩
缓存击穿
本地锁
对于缓存穿透我们可以使用锁来解决。本文讲如何使用本地锁来解决穿透问题。只适用于单体应用。
synchronized
主业务方法:
@Autowired
private StringRedisTemplate redisTemplate;
private final static String CATEGORY_JSON_KEY = "category_json_key";
public Map<Long,List<Category2Level>> getCategorysJson() {
// 先从redis缓存中查询是否有该数据
String s = redisTemplate.opsForValue().get(CATEGORY_JSON_KEY);
// 有缓存
if (!StringUtils.isEmpty(s)){
// System.out.println("缓存中有");
// 字符串转成需要的格式
Map<Long, List<Category2Level>> longListMap = JSON.parseObject(s, new TypeReference<Map<Long, List<Category2Level>>>() {
});
return longListMap;
}
// System.out.println("缓存中没有");
// 在数据库中查询数据
Map<Long, List<Category2Level>> categorysJsonFromDB = getCategorysJsonFromDB();
// 没有添加锁之前不用去掉注释,在获取数据后直接保存到redis
// 在redis缓存中存数据 在锁结束时保存到缓存中
// redisTemplate.opsForValue().set(CATEGORY_JSON_KEY,JSON.toJSONString(categorysJsonFromDB),1, TimeUnit.DAYS);
return categorysJsonFromDB;
}
解决缓存击穿问题
- 只要是同一把锁,就能锁住需要这个锁的所有线程
- synchronized(this),springboot所有的组件在容器中都是单例的
- 得到锁之后,我们应该再去缓存中确定一次,如果没有才需要继续查询
private Map<Long,List<Category2Level>> getCategorysJsonFromDB() {
synchronized (this){
// 当其他的线程进来的时候直接查询缓存,避免操作数据库
String s = redisTemplate.opsForValue().get(CATEGORY_JSON_KEY);
// 缓存中没有
if (StringUtils.isEmpty(s)){
// System.out.println("查询数据库"); getDatasFromDB();是业务逻辑
Map<Long, List<Category2Level>> datasFromDB = getDatasFromDB();
// 在redis缓存中存数据 在锁结束时保存到缓存中
redisTemplate.opsForValue().set(CATEGORY_JSON_KEY,JSON.toJSONString(datasFromDB),1, TimeUnit.DAYS);
return datasFromDB;
}
Map<Long, List<Category2Level>> longListMap = JSON.parseObject(s, new TypeReference<Map<Long, List<Category2Level>>>() {
});
return longListMap;
}
}
缺点:如果在每个机器上都部署,每个this都是一把锁,几台机器就有几个实例对象,想要锁住所有,必须使用分布式锁