Redis - 緩存穿透以及緩存雪崩
1.緩存收益以及代價
當我們使用一項技術時,我們就需要對它有一定的瞭解,知道我們爲什麼要去使用它,能夠分析使用這項技術所帶來的的回報以及我們所需要付出的代價。
緩存所帶來的收益:
- 高速讀寫:緩存會加速讀寫速度,利用CPU L1/L2/L3 Cache、Linux page Cache加速硬盤讀寫、瀏覽器緩存、
Ehcache
緩存緩存數據,其性能都會比關係型數據庫高很多,內存級別的讀寫性能大大優於磁盤級別的讀寫性能。- 降低後端負載:後端服務器業務通過使用
Redis
減少對MySQL
的請求訪問,降低了MySQL
負載等。緩存所帶來的代價:
- 數據不一致:緩存層和數據庫層面數據不一致,例如
Redis
已經對某條複雜的數據進行緩存,此時通過後臺修改該數據,由於大部分情況下我們不會主動對Redis
進行數據刪除,因爲從性能以及包容性來說都不需要進行刪除操作,所以就導致緩存數據同步不及時,這些都和我們自身應用更新策略有關,需要根據實際情況合理設置數據緩存過期時間等相關操作。- 代碼維護成本(人工成本):不適用
Redis
緩存的情況下,我們只需讀寫MySQL
就能實現功能,但當我們加入緩存之後就需要去維護緩存數據的處理邏輯,增加了代碼複雜度。某些情況下會降低項目開發以及測試效率,例如測試人員需要測試有緩存時的情況,也要測試沒有緩存時的情況,另外當測試人員測試功能無需關注緩存時需手動清除對應緩存,否則需要等待數據緩存過期,延長測試時間。- 性能風險:堆內緩存由於是存儲在本地服務器中,由JVM或者本地服務器來維護數據,可能帶來內存溢出的風險甚至影響用戶進程,如
ehCache
、loadingCache
。
堆內緩存和遠程服務器緩存如何選擇?對於這個問題主要考慮以下幾點:
- 堆內緩存一般性能更好,遠程緩存需要套接字傳輸。
- 用戶級別緩存儘量採用遠程緩存(例如集羣架構下,多臺機器會重複緩存同一組數據)。
- 大數據量儘量採用遠程緩存,避免造成應用服務器壓力過大,遵循服務節點化原則。
2.緩存穿透
在我們使用
Redis
的過程中,Redis
的確幫助我們解決了很多的問題,但是當技術和業務結合在一起時就會發現一些讓人深思的問題。緩存穿透就是其中一個。
2.1 什麼是緩存穿透?
上面這張圖是我們正常業務場景下的一個流程(客戶端->服務端[Redis->DB]),那麼緩存穿透指的是當用戶查詢數據,再緩存中不存在,並且在數據庫也不存在時,導致用戶查詢這樣的數據(或者惡意攻擊) 在緩存中找不到對應key的value,每次都需要要在數據庫中查詢一遍,然後返回空值。其實這就相當於進行了兩次無用的查詢,這樣請求就繞過緩存直接查數據庫,對數據庫造成了很大的壓力。
2.2 如何防止緩存穿透?
說到如何防止緩存穿透,這裏我主要提出兩點建議:
- 緩存空值:如果DB查詢返回數據或者業務結果爲空,此時我們仍然將空結果進行緩存,設置較短的過期時間(不超過五分鐘)。
- 採用布隆過濾器BloomFilter:事先將所有可能存在的數據哈希後放入到一個足夠大的BitMap中,若一個數據一定不存在則會被攔截掉,從而避免了對底層存儲系統的查詢壓力。關於布隆過濾器後面會寫一篇博客對其進行詳細介紹。
3.緩存雪崩
3.1 什麼是緩存雪崩?
如果緩存集中在一段時間內失效,發生大量的緩存穿透,所有查詢都落在數據庫上,就會造成了緩存雪崩。 由於原有緩存失效,新緩存未到期間所有原本應該訪問緩存的請求都需要去查詢數據庫,從而對數據庫服務CPU和內存造成巨大壓力,嚴重可能會造成數據庫服務宕機。
3.2 如何防止緩存雪崩?
這裏我們看看如何防止緩存雪崩:
- 加鎖排隊:維護一個mutex互斥鎖,通過
Redis
的SETNX
命令去設置一把當前業務操作的鎖(例如setnx lock_uid_001 value nullValue、setnx lock_white_list value nullValue),當操作成功時,再進行LoadDB操作回設緩存,否則重試get緩存。- 數據預熱:緩存預熱就是當我們新部署一個項目系統時,將相關緩存數據直接加載到緩存中。這樣可以避免在用戶請求時候,再去查詢數據庫放入緩存。用戶可以直接查詢事先被預熱的緩存數據,可以預熱大併發量、熱度高的
Key
緩存數據。- 雙層緩存策略:同一條數據我們可以維護兩套緩存,C1爲原始緩存,C2爲拷貝緩存,當C1失效時,可以讀取C2,都不存在時再去訪問數據庫回設,當然C1緩存失效時間需要設置爲短期,而C2設置爲長期。這種方式最大缺點就是佔用的內存翻倍。
- 定時更新緩存策略:對於失效性要求低的緩存,可以在容器啓動初始化時加載放入緩存,採用定時任務更新或移除緩存。
- 時間浮動策略:設置不同過期時間,讓緩存失效的時間點儘量均勻,避免集中失效。