Redis缓存穿透、击穿、雪崩问题,分布式锁的简单运用

1.一般的缓存处理流程

当前台收到请求后,后台先回冲缓存中读取数据,取到直接返回结果,当从缓存取不到结果时,就会访问数据库,从数据库取到数据更新到缓存中,并返回结果,如果数据库也没结果,就返回空。

2. 缓存穿透

描述:
访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

缓存穿透流程图如下

解决方案

  1. 对空值缓存: 如果一个查询返回的数据为空(不管数据是否不存在),任然吧这个空结果(null)进行缓存,设置空结果的超过时间会很短,一般不超过五分钟。
  2. 设置可访问的名单(白名单): 使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  3. 采用布隆过滤器:(布隆过滤器(Bloom Filter))是1970年由布隆提出的。
    它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是由一定的误识别率和删除困难。
  4. **进行实时监控:**当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

3.缓存击穿

描述:
某一个热点key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

解决方案:

  1. 预先设置热门数据: 在Redis高峰访问之前,把一些热门数据提前存入到redis中,加大这些热门数据key的时长。
  2. 实时调整: 现场监控哪些数据热门,实时调整key的过期时长。
  3. 使用锁: 在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。可以使用“分布式锁”,或“JVM 锁”

使用 redis 分布式锁的伪代码,仅供参考:

public class lock {
    @Autowired
    RedisTemplate redisTemplate;

    public void testLock(){
        //生成uuid 防止误删锁
        String uuid = UUID.randomUUID().toString();
        //定义一个锁 lua脚本可以使用同一把锁,来实现删除
        String skaId = "25";
        String lockKey = "lock"+skaId;
        //获取锁 ,setnx
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid,3, TimeUnit.SECONDS);
        //获取锁成功,查询num的值
        if (lock){
            Object value = redisTemplate.opsForValue().get("num");
            //判断num值为空return
            if (StringUtils.isEmpty(value)){
                return;
            }
            //有值就转换成int
            int num = Integer.parseInt(value + "");
            //把redis的num加1
            redisTemplate.opsForValue().set("num",num++);

            /*使用lua脚本来锁*/
            //定义lua脚本
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del',KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            //设置一下返回值类型,为long
            //因为删除判断的时候,返回的0,给其封装为数据类型,如果不封装那么默认返回string类型
            //那么返回字符串与0会有发生错误。
            redisScript.setResultType(Long.class);
            //第一个要是script脚本,第二个需要判断的key,第三个就是key锁对应的值。
            redisTemplate.execute(redisScript, Arrays.asList(lockKey),uuid);
        }else {
            //获取锁失败,每隔0.1秒再获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4.缓存雪崩

描述: 大量的热点key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时数据库大量请求、压力骤增,引起雪崩,甚至导致数据库被打挂。

解决方案:

  1. 过期时间打散: 既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
  2. 热点数据不过期: 该方式和缓存击穿一样,也是要着重考虑刷新时间的时间间隔和数据库异常如何处理的情况。
  3. 加互斥锁: 开方式和缓存击穿一样,按key维度加锁,对于同以可key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,让后直接走缓存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值