redis緩存穿透、緩存雪崩、熱點Key問題分析及解決方案

咱們一般使用 緩存 + 過時時間的策略來幫助咱們加速接口的訪問速度,減小了後端負載,同時保證功能的更新。redis

緩存穿透

緩存系統,按照KEY去查詢VALUE,KEY對應的VALUE必定不存在的時候並對KEY併發請求量很大的時候,就會對後端形成很大的壓力。sql

查詢一個必然不存在的數據。好比文章表,查詢一個不存在的id,每次都會訪問DB,若是有人惡意破壞,極可能直接對DB形成影響。數據庫

因爲緩存不命中,每次都要查詢持久層(回源),從而失去緩存的意義。後端

解決方法

  1. 緩存層緩存空值
    • 緩存太多空值,佔用更多空間。(優化:給個空值過時時間)
    • 存儲層更新代碼了,緩存層仍是空值。(優化:後臺設置時主動刪除空值,並緩存把值進去)
  2. 將數據庫中全部的查詢條件,放到 布隆過濾器 中。當一個查詢請求來臨的時候,先通過布隆過濾器進行檢查,若是請求存在這個條件中,那麼繼續執行,若是不在,直接丟棄。

注意事項:數組

​ 使用布隆過濾器時,若是數據庫中有10000個條件,那麼布隆過濾器的容量size設置的要稍微比10000大一些,好比12000。緩存

​ 對於誤判率的設置,根據實際項目,以及硬件設施來具體決定。可是必定不能設置爲0,而且誤判率設置的越小,哈希函數跟數組長度都會更多跟更長,那麼對硬件,內存中間的要求就會相應的高。併發

private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001); 異步

​ 有了size跟誤判率,那麼布隆過濾器就會產生相應的哈希函數跟數組。分佈式

綜上: 咱們能夠利用布隆過濾器,將redis緩存擊穿控制在一個可容忍的範圍內。函數

緩存雪崩(緩存失效)

若是緩存集中在一段時間內失效,發生大量的緩存穿透,全部的查詢都落在數據庫上,形成了緩存雪崩。

緩存層宕掉後,流量會像奔逃的野牛同樣,打向後端存儲

解決辦法

  1. 在緩存失效後,經過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。好比對某個key只容許一個線程查詢數據和寫緩存,其餘線程等待;
  2. 能夠經過緩存reload機制,預先去更新緩存,再即將發生大併發訪問前手動觸發加載緩存;
  3. 不一樣的key,設置不一樣的過時時間,讓緩存失效的時間點儘可能均勻;
  4. 作二級緩存,或者雙緩存策略:
    • A1爲原始緩存,A2爲拷貝緩存;
    • A1失效時,能夠訪問A2,A1緩存失效時間設置爲短時間,A2設置爲長期。

熱點key

  • 這個key是一個熱點key(例如一個重要的新聞,一個熱門的八卦新聞等等),因此這種key訪問量可能很是大。
  • 緩存的構建是須要必定時間的。(多是一個複雜計算,例如複雜的sql、屢次IO、多個依賴(各類接口)等等)

因而就會出現一個致命問題:在緩存失效的瞬間,有大量線程來構建緩存(見下圖),形成後端負載加大,甚至可能會讓系統崩潰 。

解決辦法

  1. 使用互斥鎖(mutex key):這種解決方案思路比較簡單,就是隻讓一個線程構建緩存,其餘線程等待構建緩存的線程執行完,從新從緩存獲取數據就能夠了;

  2. "提早"使用互斥鎖(mutex key):在value內部設置1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小。當從cache讀取到timeout1發現它已通過期時候,立刻延長timeout1並從新設置到cache。而後再從數據庫加載數據並設置到cache中;

  3. "永遠不過時":

    這裏的「永遠不過時」包含兩層意思

    • 從redis上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時;
    • 從功能上看,若是不過時,那不就成靜態的了嗎?因此咱們把過時時間存在key對應的value裏,若是發現要過時了,經過一個後臺的異步線程進行緩存的構建,也就是「邏輯」過時
  4. 資源保護:能夠作資源的隔離保護主線程池,若是把這個應用到緩存的構建也何嘗不可。

四種方案對比

做爲一個併發量較大的互聯網應用,咱們的目標有3個:

  • 加快用戶訪問速度,提升用戶體驗。
  • 下降後端負載,保證系統平穩。
  • 保證數據「儘量」及時更新(要不要徹底一致,取決於業務,而不是技術。)

因此第二節中提到的四種方法,能夠作以下比較,仍是那就話:沒有最好,只有最合適。

解決方案 優勢 缺點
簡單分佈式鎖(Tim yang) 1. 思路簡單<br />2. 保證一致性 1. 代碼複雜度增大<br />2. 存在死鎖的風險<br />3. 存在線程池阻塞的風險
加另一個過時時間(Tim yang) 1. 保證一致性 同上
不過時(本文) 1. 異步構建緩存,不會阻塞線程池 1. 不保證一致性。<br />2. 代碼複雜度增大(每一個value都要維護一個timekey)。<br />3. 佔用必定的內存空間(每一個value都要維護一個timekey)。
資源隔離組件hystrix(本文) 1. hystrix技術成熟,有效保證後端。<br />2. hystrix監控強大。 1. 部分訪問存在降級策略。

總結

  • 熱點key + 過時時間 + 複雜的構建緩存過程 => mutex key問題
  • 構建緩存一個線程作就能夠了。
  • 四種解決方案:沒有最佳只有最合適。

參考文獻:

相關文章
相關標籤/搜索