快取三大問題及解決方案

我一定會有貓的發表於2018-08-03

1. 快取來由

隨著網際網路系統發展的逐步完善,提高系統的qps,目前的絕大部分系統都增加了快取機制從而避免請求過多的直接與資料庫操作從而造成系統瓶頸,極大的提升了使用者體驗和系統穩定性。

2. 快取問題

雖然使用快取給系統帶來了一定的質的提升,但同時也帶來了一些需要注意的問題。

2.1 快取穿透

快取穿透是指查詢一個一定不存在的資料,因為快取中也無該資料的資訊,則會直接去資料庫層進行查詢,從系統層面來看像是穿透了快取層直接達到db,從而稱為快取穿透,沒有了快取層的保護,這種查詢一定不存在的資料對系統來說可能是一種危險,如果有人惡意用這種一定不存在的資料來頻繁請求系統,不,準確的說是攻擊系統,請求都會到達資料庫層導致db癱瘓從而引起系統故障。

2.2 解決方案

快取穿透業內的解決方案已經比較成熟,主要常用的有以下幾種:

  • bloom filter:類似於雜湊表的一種演算法,用所有可能的查詢條件生成一個bitmap,在進行資料庫查詢之前會使用這個bitmap進行過濾,如果不在其中則直接過濾,從而減輕資料庫層面的壓力。
  • 空值快取:一種比較簡單的解決辦法,在第一次查詢完不存在的資料後,將該key與對應的空值也放入快取中,只不過設定為較短的失效時間,例如幾分鐘,這樣則可以應對短時間的大量的該key攻擊,設定為較短的失效時間是因為該值可能業務無關,存在意義不大,且該次的查詢也未必是攻擊者發起,無過久儲存的必要,故可以早點失效。

2.3 快取雪崩

在普通的快取系統中一般例如redis、memcache等中,我們會給快取設定一個失效時間,但是如果所有的快取的失效時間相同,那麼在同一時間失效時,所有系統的請求都會傳送到資料庫層,db可能無法承受如此大的壓力導致系統崩潰。

2.4 解決方案

  • 執行緒互斥:只讓一個執行緒構建快取,其他執行緒等待構建快取的執行緒執行完,重新從快取獲取資料才可以,每個時刻只有一個執行緒在執行請求,減輕了db的壓力,但缺點也很明顯,降低了系統的qps。
  • 交錯失效時間:這種方法時間比較簡單粗暴,既然在同一時間失效會造成請求過多雪崩,那我們錯開不同的失效時間即可從一定長度上避免這種問題,在快取進行失效時間設定的時候,從某個適當的值域中隨機一個時間作為失效時間即可。

2.5 快取擊穿

快取擊穿實際上是快取雪崩的一個特例,大家使用過微博的應該都知道,微博有一個熱門話題的功能,使用者對於熱門話題的搜尋量往往在一些時刻會大大的高於其他話題,這種我們成為系統的“熱點“,由於系統中對這些熱點的資料快取也存在失效時間,在熱點的快取到達失效時間時,此時可能依然會有大量的請求到達系統,沒有了快取層的保護,這些請求同樣的會到達db從而可能引起故障。擊穿與雪崩的區別即在於擊穿是對於特定的熱點資料來說,而雪崩是全部資料。

2.6 解決方案

  • 二級快取:對於熱點資料進行二級快取,並對於不同級別的快取設定不同的失效時間,則請求不會直接擊穿快取層到達資料庫。
  • 這裡參考了阿里雙11萬億流量的快取擊穿解決方案,解決此問題的關鍵在於熱點訪問。由於熱點可能隨著時間的變化而變化,針對固定的資料進行特殊快取是不能起到治本作用的,結合LRU演算法能夠較好的幫我們解決這個問題。那麼LRU是什麼,下面粗略的介紹一下,有興趣的可以點選上面的連結檢視.
    • LRU(Least recently used,最近最少使用)演算法根據資料的歷史訪問記錄來進行淘汰資料,其核心思想是“如果資料最近被訪問過,那麼將來被訪問的機率也更高”。最常見的實現是使用一個連結串列儲存快取資料,如下圖所示快取三大問題及解決方案
      這個連結串列即是我們的快取結構,快取處理步驟為
      • 首先將新資料放入連結串列的頭部
      • 在進行資料插入的過程中,如果檢測到連結串列中有資料被再次訪問也就是有請求再次訪問這些資料,那麼就其插入的連結串列的頭部,因為它們相對其他資料來說可能是熱點資料,具有保留時間更久的意義
      • 最後當連結串列資料放滿時將底部的資料淘汰,也就是不常訪問的資料
    •  LRU-K演算法 ,其實上面的演算法也是該演算法的特例情況即LRU-1,上面的演算法存在較多的不合理性,在實際的應用過程中採用該演算法進行了改進,例如偶然的資料影響會造成命中率較低,比如某個資料即將到達底部即將被淘汰,但由於一次的請求又放入了頭部,此後再無該資料的請求,那麼該資料的繼續存在其實是不合理的,針對這類情況LRU-K演算法擁有更好的解決措施。結構圖如下所示:快取三大問題及解決方案

      LRU-K需要多維護一個佇列或者更多,用於記錄所有快取資料被訪問的歷史。只有當資料的訪問次數達到K次的時候,才將資料放入快取。當需要淘汰資料時,LRU-K會淘汰第K次訪問時間距當前時間最大的資料。

      • 第一步新增資料照樣放入第一個佇列的頭部
      • 如果資料在該佇列裡訪問沒有達到K次(該數值根據具體系統qps來定)則會繼續到達連結串列底部直至淘汰;如果該資料在佇列中時訪問次數達到了K次,那麼它會被加入到接下來的2級(具體需要幾級結構也同樣結合系統分析)連結串列中,按照時間順序在2級連結串列中排列
      • 接下來2級連結串列中的操作與上面演算法相同,連結串列中的資料如果再次被訪問則移到頭部,連結串列滿時,底部資料淘汰
相比LRU,LRU-K需要多維護一個佇列,用於記錄所有快取資料被訪問的歷史,所以需要更多的記憶體空間來用來構建快取,但優點也很明顯,較好的降低了資料的汙染率提高了快取的命中率,對於系統來說可以用一定的硬體成本來換取系統效能也不失為一種辦法。當然還有更為複雜的快取結構演算法,點選LRU演算法即可學習,例如Two Queues和Mutil Queues等等,本文不過多贅述,只為讀者提供一種解決思路。


參考文章: 雙11萬億流量下的分散式快取 https://yq.aliyun.com/articles/290865
          快取淘汰演算法 http://flychao88.iteye.com/blog/1977653
複製程式碼





相關文章