在使用redis作为缓存中间件时,在高并发的情况下,会出现缓存失效的问题。 缓存穿透、缓存雪崩、缓存击穿。
对于缓存穿透我们可以使用锁来解决。本文讲如何使用本地锁来解决穿透问题。只适用于单体应用。
主业务方法:
@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都是一把锁,几台机器就有几个实例对象,想要锁住所有,必须使用分布式锁