缓存穿透,缓存击穿,缓存雪崩(原理 + 标准代码)

缓存穿透

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

解决方案:如果是mysql中没有相关数据,那么针对这个key在redis中给这个key设置一个"NULL",相当于标识数据库中没有这条数据,不要再去做无用的查询了,直接返回结果给方法调用方,同时在mysql插入这条数据的时候要及时清理掉这条缓存,否则这条缓存将成为脏数据,其次,再设置这条缓存的时候也要设置过期时间,防止这个缓存一直存在

缓存击穿

某一时间缓存过期,如果期间大量的请求到来,且遇到了慢sql查询,那么会使得一瞬间mysql压力陡增

解决方案:使用分布式锁,使用业务锁标识 + 缓存中的key拼接作为锁,高并发场景下,第一个线程抢占了这个锁,即通过setnx对这个锁设置了值,才能去查mysql,并将结果存到缓存,其余线程如果是要使用同样的key查询,那么再获取锁的,即通过setnx对这个锁设置值的时候失败,这种时候可以睡眠一段时间重新递归调用,可以限制最大重试次数,如果超过最大次数说明次数分布式锁出现异常,打印日志并避开缓存直接去查mysql获取结果

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。有一个简单方案就时将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。或者Redis查DB数据的时候将请求加一个随机延迟时间,让请求不要集中。

标准代码

   private static Jedis jedis;
    /**
     *
     * @param key:redis缓存中的键
     * @param supplier:供给者对象,它的匿名实现类的get方法中执行sql方法,并返回查询mysql的结果
     * @param expiredTime:过去时间,不宜太短,否则遇到慢sql
     * @return
     */
    public static <T> Object getData(String key, Supplier<T> supplier, long expiredTime, int count) throws InterruptedException {
        Object data = jedis.get(key);
        if (data != null) {
            // 如果在缓存中找到数据NULL,说明当前数据库中也没有这个值,直接返回,预防缓存穿透
            if ("NULL".equals(data)) {
                return null;
            } else {
                log.info("get data from redis:{}", key);
                return data;
            }
        } else {
            // 此处相当于利用setnx的特征模拟出一个类似于争夺锁的场景,只有获取到锁的线程,才会去数据库中取数据,并设置到缓存,
            // 其它线程统一先睡一段时间再去尝试去缓存中取数据,用来预防缓存击穿

            // 此处key的过期时间不宜太短,否则遇到慢sql,再未执行完慢sql时,且当前线程执行方法因为循环没有返回null,那么这个线程
            // 会获取到这个锁,再去执行慢sql,这样即使之前的线程已经执行完毕了,但循环睡眠中的线程依然获取不到锁,那这样预防,这样也会一定程度上
            if ("OK".equals(jedis.set(key, "lock", new SetParams().nx().ex(180l)))) {
                //执行sql
                T t = supplier.get();
                log.info("get data from db key:{}", key);
                // 如果在缓存中没有找到数据,查询数据库
                if (t != null) {
                    String json = new Gson().toJson(t);
                    // 缓存时间在设置的时间expiredTime的基础上加上一个上限为1/10过期时间的随机数。防止缓存雪崩
                    long time = expiredTime + new Random(expiredTime / 10).nextLong();
                    // 如果在数据库中找到数据,将数据缓存到Redis,并返回数据
                    jedis.setex(key, time, json);
                    return json;
                } else {
                    // 如果在数据库中也没有找到数据,将"NULL"缓存到Redis,并返回null,这样可以防止缓存击透
                    jedis.setex(key, 60, "NULL");
                    return null;
                }
            } else {
                Thread.sleep(50);
                data = jedis.get(key);
                if (Objects.nonNull(data)) {
                    return data;
                }

                
                while (count-- > 0) {
                    getData(key, supplier, expiredTime, count);
                }
                // 这种情况可能分布式锁存在问题,或执行可能是存在问题,打印报警日志,并尝试去库中获取结果
                if (count == -1) {
                    T t = supplier.get();
                    log.warn()
                }  
            }

        }
        return data;
    }
缓存穿透缓存击穿缓存雪崩是常见的缓存问题,下面是关于Redis的缓存穿透缓存击穿缓存雪崩的介绍: 1. 缓存穿透缓存穿透是指当一个请求查询一个不存在于缓存中的数据时,由于缓存无法命中,请求会直接访问数据库。这种情况下,如果有大量的请求查询不存在的数据,会导致数据库压力过大,影响系统性能。 2. 缓存击穿缓存击穿是指当一个热点数据的缓存过期或失效时,大量的请求同时访问该数据,导致缓存无法命中,请求会直接访问数据库。这种情况下,数据库会承受巨大的压力,可能导致数据库崩溃。 3. 缓存雪崩缓存雪崩是指当缓存中的大量数据同时过期或失效时,大量的请求会直接访问数据库,导致数据库压力剧增,性能下降甚至系统崩溃。缓存雪崩通常是由于缓存服务器故障、缓存设置不合理或者缓存数据过期时间设置不当等原因引起的。 为了避免缓存穿透缓存击穿缓存雪崩问题,可以采取以下措施: - 缓存穿透:可以在应用层对查询的数据进行校验,如果数据不存在,则不进行缓存操作,避免大量无效的请求访问数据库。 - 缓存击穿:可以互斥锁或分布式锁来保护热点数据的问,当缓存失效时,只允许一个请求访问数据库并更新缓存,其他请求等待缓存更新完成后再从缓存中获取数据。 - 缓存雪崩:可以采用多级缓存缓存预热、设置合理的缓存过期时间等策略来避免大量缓存同时失效,保证系统的稳定性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值