經典問題:緩存穿透/熱點數據集中失效/雪崩/一致性問題

緩存系統一定程度上極大提升系統併發能力,但同樣也增加系統的複雜度,下面針對緩存系統設計與使用中面臨的常見問題展開。主要是針對熱門問題:緩存穿透/熱點數據集中失效/雪崩/一致性問題的總結及解決方案。

There are only two hard things in Computer Science: cache invalidation and naming things.           – Phil Karlton

計算機科學中有兩件難事:緩存失效和命名 

緩存穿透

什麼是緩存穿透?

緩存穿透是指訪問不存在數據,從而繞過緩存,直取數據源(大量數據源讀取操作)

穿透帶來的問題?

大量的請求到數據庫去查詢,可能會導致你的數據庫由於壓力過大而宕掉。

解決辦法?

1.緩存空值

思路:緩存中沒有存儲這些空數據的key,導致每次查詢都到數據庫去了,那麼我們就可以爲這些key對應的值設置爲null 丟到緩存裏面去,後面再出現查詢這個key 的請求的時候,直接返回null 。

2. BloomFilter

BloomFilter 類似於一個hbase set 用來判斷某個元素(key)是否存在於某個集合中。

這種方式在大數據場景應用比較多,比如 Hbase 中使用它去判斷數據是否在磁盤上,還有在爬蟲場景判斷url 是否已經被爬取過。

這種方案可以加在第一種方案中,在緩存之前在加一層 BloomFilter (在查詢的時候先去 BloomFilter 去查詢 key 是否存在,如果不存在就直接返回,存在再走查緩存 -> 查 DB)。

小結

針對於一些惡意攻擊,攻擊帶過來的大量key 是不存在的,那麼我們採用第一種方案就會緩存大量不存在key的數據。此時我們採用第一種方案就不合適了,我們完全可以先對使用第二種方案進行過濾掉這些key。

針對這種key異常多、請求重複率比較低的數據,我們就沒有必要進行緩存,使用第二種方案直接過濾掉。

而對於空數據的key有限的,重複率比較高的,我們則可以採用第一種方式進行緩存。

緩存擊穿(熱點數據集中失效)

什麼是擊穿(熱點數據集中失效)?

在平常高併發的系統中,大量的請求同時查詢一個 key 時,此時這個key正好失效了,就會導致大量的請求都打到數據庫上面去。這種現象我們稱爲緩存擊穿

會帶來什麼問題?

會造成某一時刻數據庫請求量過大,壓力劇增。

如何解決?

1. 設置不同的失效時間

爲了避免這些熱點的數據集中失效,那麼我們在設置緩存過期時間的時候,我們讓他們失效的時間錯開。比如在一個基礎的時間上加上或者減去一個範圍內的隨機值。

2. 互斥鎖

多個線程同時去查詢數據庫的這條數據,那麼我們可以在第一個查詢數據的請求上使用一個 互斥鎖來鎖住它。其他的線程走到這一步拿不到鎖就等着,等第一個線程查詢到了數據,然後做緩存。

緩存雪崩

什麼是緩存雪崩?

緩存雪崩是指緩存系統失效,導致大量請求同時進行數據回源,導致數據源壓力驟增而崩潰。兩種情況會導致此問題:

  1. 多個緩存數據同時失效
  2. 緩存系統崩潰

解決辦法?

事前:使用集羣緩存,保證緩存服務的高可用,這種方案就是在發生雪崩前對緩存集羣實現高可用,如果是使用 Redis,可以使用 主從+哨兵 ,Redis Cluster 來避免 Redis 全盤崩潰的情況。

事中:ehcache本地緩存 + Hystrix限流&降級,避免MySQL被打死,使用 ehcache 本地緩存的目的也是考慮在 Redis Cluster 完全不可用的時候,ehcache 本地緩存還能夠支撐一陣。使用 Hystrix進行限流 & 降級 ,比如一秒來了5000個請求,我們可以設置假設只能有一秒 2000個請求能通過這個組件,那麼其他剩餘的 3000 請求就會走限流邏輯。

事後:開啓Redis持久化機制,儘快恢復緩存集羣,一旦重啓,就能從磁盤上自動加載數據恢復內存中的數據。

緩存更新與數據一致性

常見緩存更新策略:

  • 讀操作:命中緩存則返回,無緩存則取回源數據,寫緩存
  • 寫操作:先刪除緩存,再更新數據源

會帶來什麼問題?

讀寫併發的場景下先刪緩存操作可能導致髒數據入緩存。比如:

  • 線程A 寫操作:刪除緩存
  • 線程B 讀操作:無緩存則取回源數據(舊數據),回寫緩存(此時緩存中爲舊數據)
  • 線程A 寫操作:更新數據源
  • 此時緩存數據不一致:緩存中爲舊數據,數據源爲新數據,出現緩存舊數據問題

解決辦法?

1. Cache Aside Pattern:先數據源更新後,再失效緩存(由等待下次讀取來回寫緩存)

  • 優勢:無緩存舊數據問題、緩存系統維護簡單、Facebook推薦方案
  • 問題:無法絕對杜絕併發讀寫問題,這種問題出現概率極低,幾點要求:緩存已過期、併發讀寫、讀數據比寫數據快、但讀操作更新緩存比寫操作失效緩存慢(也就是說寫操作的行爲需完全發生在讀操作兩步之間),一般而言讀操作(讀庫+更新緩存)時長要小於寫操作(更新數據源+失效緩存),所以認爲這種併發問題概率較低。可以通過增加鎖機制,解決併發問題。

2. Read Through Pattern:更新數據源由緩存系統操作

  • 讀取數據時,如緩存失效,則緩存服務取回源數據更新緩存
  • 而Cache Aside中是由應用服務(調用方)更新緩存
  • 這套對調用方是透明的,只有一套存儲系統,而無視緩存、數據源的差異

3. Write Through Pattern:更新數據源由緩存系統操作

  • 寫數據時,如緩存失效,則直接更新數據源(不做任何緩存操作);如命中緩存,則更新緩存(由緩存系統更新數據源)
  • 在緩存失效下寫操作的處理後,何時更新緩存呢?下一次讀操作,按Read Through中緩存失效策略來更新緩存

4. Write Behind Caching Pattern:又稱Write Back

一句話總結:更新數據時,只更新緩存,不更新數據源(緩存異步批量更新數據源)

優勢:

  • 更新緩存爲內存操作,讀寫I/O非常高
  • 異步批量更新數據源,合併多個操作

問題:

  • 緩存不滿足強一致性要求
  • 強一致性和高性能的衝突高可用和高性能的衝突終究會使Trade-Off
  • 實現複雜,需跟蹤哪些Cache更新,成本較高

一般而言,推薦Cache Aside Pattern方案,容忍較小概率的不一致(同時也可以增加鎖機制解決此低概率併發問題),簡化緩存系統複雜度。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章