如何防止緩存穿透、緩存擊穿、緩存雪崩

一般緩存使用流程

緩存簡單使用流程.png

一般的緩存使用流程:

  1. 從緩存中查詢數(shù)據(jù)
  2. 判斷緩存中數(shù)據(jù)是否存在
  3. 存在則直接返回?cái)?shù)據(jù)
  4. 數(shù)據(jù)不存在,查詢數(shù)據(jù)庫
  5. 判斷數(shù)據(jù)庫中數(shù)據(jù)是否存在
  6. 存在更新緩存,返回結(jié)果
  7. 不存在直接返回null
    private static String prefix = "product_cache_product";

   @Override
    public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
        if (StringUtils.isBlank(skuCode)) {
            return null;
        }
        //查詢緩存
        SkuDetailEntity skuEntity = cache.query(Key.valueOf(prefix, skuCode));
        //緩存未命中
        if (Objects.isNull(skuEntity)) {
            //查詢數(shù)據(jù)庫
            skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
            //數(shù)據(jù)庫命中
            if(Objects.nonNull(skuEntity)){
                //將結(jié)果更新至緩存 緩存時(shí)間為300秒
                cache.save(Key.valueOf(prefix, skuCode),skuEntity,300);
            }
        }
        return skuEntity;
    }

緩存穿透

緩存穿透:是指請求的數(shù)據(jù)即沒有命中緩存也沒有命中DB中(如:黑客偽造skuCode發(fā)送大量的請求),此時(shí)請求會直接作用的DB上,流量大時(shí)可造成DB宕機(jī)。即有緩存屏障但是被惡意繞過,穿透:從縫隙、漏洞中穿過,此處漏洞即為請求的數(shù)據(jù)更本不存在。
緩存穿透發(fā)生步步驟:1、2、4、5、7
解決方法:
1.對請求來的參數(shù)進(jìn)行合法校驗(yàn),如:有規(guī)則的skuCode可以對skuCode進(jìn)行校驗(yàn)
2.對null也進(jìn)行緩存
如:

 private static String prefix = "product_cache_product";

   @Override
    public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
        if (StringUtils.isBlank(skuCode)) {
            return null;
        }
        //查詢緩存
        Optional<SkuDetailEntity> optional = cache.query(Key.valueOf(prefix, skuCode));
        //緩存未命中
        if (Objects.isNull(optional)) {
            //查詢數(shù)據(jù)庫
            SkuDetailEntity skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
            optional = Optional.fromNullable(skuEntity);
            //緩存5分鐘
            cache.save(Key.valueOf(prefix, skuCode),optional,300);
        }
        return optional.orNull();
    }

緩存擊穿

緩存擊穿:是指請求的數(shù)據(jù)沒有命中緩存但是命中數(shù)據(jù)庫,當(dāng)大量的請求在極短的時(shí)間段內(nèi)一起請求某條數(shù)據(jù)的緩存并都未命中(如:緩存失效時(shí)還存在大量的請求,熱點(diǎn)數(shù)據(jù)沒有預(yù)熱等),這些請求會繼續(xù)作用到數(shù)據(jù)庫(DB)上,造成DB壓力劇增,甚至宕機(jī)。即有緩存屏障但是屏障沒有攔截住流量,擊穿:打孔,此時(shí)數(shù)據(jù)是存在的只不過并未緩存。
緩存擊穿發(fā)生步步驟:1、2、3、4、5、6
解決方法:
1.熱點(diǎn)數(shù)據(jù)不敏感或變化不頻繁的情況下可以對熱點(diǎn)數(shù)據(jù)永久緩存
2.添加分布式鎖
如:

   private static String prefix = "product_cache_product";
   private static String lockPrefix = "product_cache_product_lock";

   @Override
    public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
        if (StringUtils.isBlank(skuCode)) {
            return null;
        }
        //查詢緩存
        SkuDetailEntity skuEntity = cache.query(Key.valueOf(prefix, skuCode));
        //緩存未命中
        if (Objects.isNull(skuEntity)) {
            try {
                //加鎖
                lock.lock(Key.valueOf(lockPrefix, skuCode), 5L, 10L);
                //再次查詢緩存
                skuEntity = cache.query(Key.valueOf(prefix, skuCode));
                if(Objects.isNull(skuEntity)){
                    //查詢數(shù)據(jù)庫
                    skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
                    //數(shù)據(jù)庫命中
                    if(Objects.nonNull(skuEntity)){
                        //將結(jié)果更新至緩存 緩存時(shí)間為300秒
                        cache.save(Key.valueOf(prefix, skuCode),skuEntity,300);
                    }
                }
            } catch (Exception e) {
                LogUtil.error("數(shù)據(jù)查詢異常", e);
            } finally {
                try {
                    //釋放鎖
                    lock.unlock(Key.valueOf(lockPrefix, skuCode));
                } catch (Exception e) {
                    LogUtil.error("釋放鎖異常", e);
                }
            }

        }
        return skuEntity;
    }

緩存雪崩

緩存雪崩:是指請求的不同數(shù)據(jù)沒有命中緩存但是命中數(shù)據(jù)庫,當(dāng)大量的請求在極短的時(shí)間段內(nèi)一起請求不同數(shù)據(jù)的緩存,并都未命中(如:大批量的數(shù)據(jù)緩存到期,熱點(diǎn)數(shù)據(jù)沒有預(yù)熱等),這些請求會繼續(xù)作用到數(shù)據(jù)庫(DB)上,造成DB壓力劇增,甚至宕機(jī)。緩存擊穿是指并發(fā)查詢某一條數(shù)據(jù),緩存雪崩是指不同數(shù)據(jù)都過期,查詢這些數(shù)據(jù)時(shí)都查不到
解決方法:
1.設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過期。
2.緩存不同數(shù)據(jù)時(shí)過期時(shí)間可以設(shè)置為隨機(jī)時(shí)間,防止同一時(shí)間大量不同緩存數(shù)據(jù)過期現(xiàn)象發(fā)生。

防止緩存穿透、擊穿、雪崩實(shí)例:

   private static String prefix = "product_cache_product";
   private static String lockPrefix = "product_cache_product_lock";

   @Override
    public SkuDetailEntity findEntityBySkuCodeWithCache(String skuCode) {
        if (StringUtils.isBlank(skuCode)) {
            return null;
        }
        //查詢緩存
        Optional<SkuDetailEntity> optional = cache.query(Key.valueOf(prefix, skuCode));
        //緩存未命中
        if (Objects.isNull(optional)) {
            try {
                //加鎖
                lock.lock(Key.valueOf(lockPrefix, skuCode), 5L, 10L);
                //再次查詢緩存
                optional = cache.query(Key.valueOf(prefix, skuCode));
                if(Objects.isNull(optional)){
                    //查詢數(shù)據(jù)庫
                    SkuDetailEntity skuEntity = skuInfoMapper.selectDetailByCode(skuCode);
                    optional = Optional.fromNullable(skuEntity);
                    //緩存200+隨機(jī)數(shù)秒
                    cache.save(Key.valueOf(prefix, skuCode),optional,200+(new Random().nextInt(100));
                 }
            } catch (Exception e) {
                LogUtil.error("數(shù)據(jù)查詢異常", e);
            } finally {
                try {
                    //釋放鎖
                    lock.unlock(Key.valueOf(lockPrefix, skuCode));
                } catch (Exception e) {
                    LogUtil.error("釋放鎖異常", e);
                }
            }

        }
        return optional.orNull();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。