緩存穿透
介紹
緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
解決方案
有很多種方法可以有效地解決緩存穿透問題,最常見的則是:
- 通過布隆過濾器攔截,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
- 對空結果進行緩存,但是過期時間很短,不超過5分鍾。如果一個查詢返回的數據為空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鍾。
緩存雪崩
介紹
緩存雪崩是指在設置緩存時采用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
解決方案
- 緩存失效時的雪崩效應對底層系統的沖擊非常可怕,大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線程(進程)寫,從而避免失效時大量的並發請求落到底層儲存系統上。
- 在緩存的失效時間基礎上增加一個隨機值,比如1-5分鍾,將緩存失效時間分散開,這樣每一個緩存的過期時間的重復率就會降低,就很難引發集體失效的事件。
緩存擊穿
介紹
一個存在的key,在緩存過期的一刻,同時有大量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量大、壓力驟增。
- 一個熱點key(例如一個重要的新聞,一個熱門的八卦新聞等等),所以這種key訪問量可能非常大。
- 緩存的構建是需要一定時間的(可能是一個復雜計算,例如復雜的sql、多次IO、多個依賴、各種接口等等)。
以上兩個問題同時出現,可能會對系統造成一個致命問題:在緩存失效的瞬間,有大量線程來構建緩存,造成后端負載加大,甚至可能會讓系統崩潰 。
解決方案
- 使用互斥鎖(mutex key):只讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據就可以了,如果是單機,可以用synchronized或者lock來處理,如果是分布式環境可以用分布式鎖就可以了(如:可以用memcache的add, redis的setnx, zookeeper的添加節點操作)。
- 提前使用互斥鎖(mutex key):在value內部設置1個超時值(timeout1), timeout1比實際的超時間時間timeout(timeout2)小。當從cache讀取到timeout1發現它已經過期時候,馬上延長timeout1並重新設置到cache。然后再從數據庫加載數據並設置到cache中。
- 將key設置成永遠不過期,可以設置緩存沒有過期時間或將過期時間存在key對象的value里,如果發現要過期了,通過一個后台的異步線程進行緩存構建,更新值及過期時間。
- 資源保護,如hystrix,可以做資源的隔離保護主線程池
四種方案對比
一個並發量較大的互聯網應用,目標有3個:
- 加快用戶訪問速度,提高用戶體驗。
- 降低后端負載,保證系統平穩。
- 保證數據“盡可能”及時更新(要不要完全一致,取決於業務,而不是技術)。
| 解決方案 | 優點 | 缺點 |
|---|---|---|
| 簡單分布式鎖 | 思路簡單、保證一致性 | 代碼復雜度增大、存在死鎖的風險、存在線程池阻塞的風險 |
| 加另外一個過期時間 | 保證一致性 | 代碼復雜度增大、存在死鎖的風險、存在線程池阻塞的風險 |
| 永遠不過期 | 異步構建緩存,不會阻塞線程池 | 不保證一致性、代碼復雜度增大(每個value都要維護一個timekey)、占用一定的內存空間(每個value都要維護一個timekey) |
| 資源隔離組件hystrix | hystrix技術成熟,有效保證后端、hystrix監控強大 | 部分訪問存在降級策略 |
