缓存穿透、缓存击穿、缓存雪崩问题+解决方案

一、缓存?

        对于一些经常访问但数据实时性不高不经常改动的数据,我们希望通过一些方式减少数据库的访问压力,缓存便起到了很重要的作用。

        在查询数据之前,我们先通过查询缓存(redis等)获取数据,如果获取到数据,则返回数据,如果获取不到数据,就对数据库进行查询,将查询到的结果先放入缓存中,然后再返回数据,下次再查询时就会从缓存中获取数据,于是大大减少了数据库的压力。

        但是对于缓存也会存在许多的问题,常见的就有缓存穿透、缓存击穿、缓存雪崩等。

二、缓存穿透?

        缓存穿透是指查询一个不存在的数据,由于缓存不命中,于是去查询数据库,但是数据库也没有这个数据,就导致了每次查询这个不存在的数据都要到数据库中去查询,缓存就失失去了作用

        风险:恶意利用不存在的数据对数据库进行攻击,导致数据库压力增大,造成卡顿甚至是崩溃。

        解决方法:①、对接口参数进行校验、防止出现恶意攻击;②、查询不到值时,将value设置成一个标记为加入缓存中,下次再查询就返回一个标记数而不必经过数据库,例如查询id为5的商品,不存在则返回一个-9999,然后在做逻辑判断,但是需要设置一个较短的缓存有效时间,防止以后key对应的value有数据的时候仍然返回空造成错误。③、使用bitmap类型定义一个可以访问的白名单,id作为偏移量。④、采用布隆过滤器

三、缓存雪崩?

        缓存雪崩是指我们设置缓存时key采用了相同的到期时间,导致在某一时刻缓存同时失效,请求全部转到了数据,数据库压力过大导致崩溃。和缓存击穿不同的是,缓存击穿指并发查同一条不存在的数据而使缓存失效,数据库压力增大;缓存雪崩是指大量的key同时过期,导致直接查询数据库压力增大导致崩溃。

        解决方法:①、设置热点数据永不过期;②、将缓存过期时间设置成某一段时间内的随机数,这样就不会同时过期;③、分布式处理缓存,将缓存存在不同的地方。

        设置随机数过期时间,时间单位为天

Random random = new Random();
int  i= random.nextInt(10);
stringRedisTemplate.opsForValue().set("catelogJSON", JSON.toJSONString(parent_cid), i, TimeUnit.DAYS);

四、缓存击穿?

        缓存击穿是指对于一些设置了过期时间的key,这些key可能在某些时间被超高并发访问,是一种’热点‘数据,然后在这个数据被访问前正好key失效了,那么对这个key的查询会全部转到数据库上,造成数据库压力增大导致卡顿崩溃的现象。

         解决方法:①、设置热点数据永不过期;②、加锁,大量并发只让一个人去查,其他人等待,直到以后释放锁,其他人读取到锁先查缓存。

        本地锁

 String data = stringRedisTemplate.opsForValue().get("data");//先从缓存中查数据

        if (!StringUtils.isEmpty(data)) {  //如果不为空,则直返回
            return JSON.parseObject(data, new TypeReference< List<CategoryEntity>>() {});
        }else { //缓存中无数据
            synchronized (this) { //设置锁
                // 双重检查 是否有缓存  (由于并发量较高,当大并发查询缓存无数据之后,进入该方法后应该再次查询是否有数据,有则返回)
                data = stringRedisTemplate.opsForValue().get("data");
                if (!StringUtils.isEmpty(data)) {
                    return JSON.parseObject(data, new TypeReference< List<CategoryEntity>>() {
                    });
                }
                List<CategoryEntity> entityList = baseMapper.selectList(null);

                Random random = new Random();
                int  i= random.nextInt(10);
                stringRedisTemplate.opsForValue().set("data", JSON.toJSONString(entityList), i, TimeUnit.DAYS);//将数据存入缓存中
                return entityList;
            }
        }

        使用redis的setnx作为分布式锁

public Map<String, List<Catelog2Vo>> getCatelogJsonFromDBWithRedisLock() {
		// 1.占分布式锁  设置这个锁10秒自动删除 [原子操作]
		String uuid = UUID.randomUUID().toString();
		Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);

		if (lock) {
			// 2.设置过期时间加锁成功 获取数据释放锁 [分布式下必须是Lua脚本删锁,不然会因为业务处理时间、网络延迟等等引起数据还没返回锁过期或者返回的过程中过期 然后把别人的锁删了]
			Map<String, List<Catelog2Vo>> data;
			try {
				data = getDataFromDB();  //处理业务
			} finally {
				// 删除也必须是原子操作 Lua脚本操作 删除成功返回1 否则返回0
				String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
				// 原子删锁
				stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
			}
			return data;
		} else {
			// 重试加锁
			try {
				// 登上两百毫秒
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return getCatelogJsonFromDBWithRedisLock();
		}
	}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值