使用緩存一些常見的套路問題。
緩存穿透
- 場景:大量請求訪問某個不存在的KEY
在緩存設計中,查詢緩存 -> key不存在 -> 回源DB -> 更新緩存,這是一個典型的方案。
緩存穿透是指查詢一個一定不存在的Key,由於緩存層不存在,將導致這個不存在的數據每次請求都要到存儲層去查詢,直接對DB造成影響。在惡意攻擊和失敗回調中可能會出現這種情況。
- 解決方案
1.對空對象進行緩存。對查詢結果為空的情況也進行緩存,如當此查詢結果為空,設置Key對應對象為NULL,緩存時間設置短一點,存儲層中有數據后及時更新。
2.對所有可能查詢的參數Key以hash形式存儲,在控制層先進行校驗,不符合則丟棄。最常見的是采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。比較適合命中不高,但是更新不頻繁的數據。
緩存失效
- 場景:緩存中大量的Key集中在一段時間內失效,數據庫的壓力凸顯
- 解決方案
1.可以分析用戶行為,盡量讓失效時間點均勻分布。針對失效時間相同的key,在設置失效時間時不設置固定的時間,而在原有基礎上加上一個隨機的值,比如1分鍾-5分鍾,這樣就可以有效分散開緩存失效的時間。
2.考慮用加鎖或者隊列的方式保證緩存的單線程(進程)寫,從而避免失效時大量的並發請求落到底層存儲系統上。
緩存並發
- 場景:在高並發場景下,某些業務有可能多個請求並發的去從數據庫獲取數據
有時候如果網站並發訪問高,一個緩存如果失效,可能出現多個進程同時查詢DB,同時設置緩存的情況,如果並發確實很大,這也可能造成DB壓力過大。
- 解決方案
1.添加分布式鎖,在緩存更新或者過期的情況下,先嘗試獲取到鎖,當更新或者從數據庫獲取完成后再釋放鎖,其他的請求只需要犧牲一定的等待時間,即可直接從緩存中繼續獲取數據。
2.定期從DB里查詢數據,再刷到緩存里面,確保緩存里面的數據一直可以讀到。
緩存雪崩
- 場景:當發生大量的緩存穿透,例如緩存掛掉,或者對某個失效的緩存的大並發訪問
由於緩存扛了大量的請求,有效保護了數據庫的安全。但是當緩存雪崩,所用請求就會瞬間全部打到DB上,可能會導致數據庫崩潰。
- 解決方案
1.保證緩存服務的高可用性,當一個實例掛掉的時候,請求也可以轉移到集群的其他實例上。緩存失效時的雪崩效應對底層系統的沖擊非常大,這時候可以使用雙緩存機制,在工作緩存之外另外維護一層災備緩存。
2.使用降級策略,當緩存服務出現問題時,可以暫時對用戶展示一份固定的數據,避免系統的崩潰,等待緩存服務的恢復。前端也應該有此機制,比如當后端接口返回非正常數據時,將之前保存的舊數據固定展示給用戶,避免頁面崩潰的問題。
緩存數據的淘汰
緩存淘汰的策略有兩種:
1.定時去清理過期的緩存
2.當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據並更新緩存
兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較復雜,具體用哪種方案,根據自己的應用場景來權衡。
更新緩存還是淘汰緩存
什么是更新緩存:數據不但寫入數據庫,還會寫入緩存
什么是淘汰緩存:數據只會寫入數據庫,不會寫入緩存,只會把數據淘汰掉
更新緩存的優點:緩存不會增加一次miss,命中率高
淘汰緩存的優點:簡單
先操作數據庫還是先操作緩存

假設先寫數據庫,再淘汰緩存:第一步寫數據庫操作成功,第二步淘汰緩存失敗,則會出現DB中是新數據,Cache中是舊數據,數據不一致。
假設先淘汰緩存,再寫數據庫:第一步淘汰緩存成功,第二步寫數據庫失敗,則只會引發一次Cache miss。
所以結論是:先淘汰緩存,再寫數據庫
參考:
