參考地址:
JavaGuide
緩存穿透,緩存擊穿,緩存雪崩解決方案分析
1.緩存穿透
一般是黑客故意去請求緩存中不存在的數據,導致所有的請求都落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
解決方案
1.最常見的則是采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
2.另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數據為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
2.緩存雪崩
緩存同一時間大面積的失效,所以,后面的請求都會落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
解決方案
事前:盡量保證整個 redis 集群的高可用性,發現機器宕機盡快補上。選擇合適的內存淘汰策略。
事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL崩掉
事后:利用 redis 持久化機制保存的數據盡快恢復緩存
3.緩存擊穿
對于一些設置了過期時間的key,如果這些key可能會在某些時間點被超高并發地訪問,是一種非常“熱點”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在于這里針對某一key緩存,前者則是很多key。
緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的并發請求過來,這些請求發現緩存過期一般都會從后端DB加載數據并回設到緩存,這個時候大并發的請求可能會瞬間把后端DB壓垮。
解決方案
1.使用互斥鎖(mutex key)
這種解決方案思路比較簡單,就是只讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據就可以了
如果是單機,可以用synchronized或者lock來處理,如果是分布式環境可以用分布式鎖就可以了(分布式鎖,可以用memcache的add, redis的setnx, zookeeper的添加節點操作)。
2. "提前"使用互斥鎖(mutex key):
在value內部設置1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小。當從cache讀取到timeout1發現它已經過期時候,馬上延長timeout1并重新設置到cache。然后再從數據庫加載數據并設置到cache中。
3. "永遠不過期":
這里的“永遠不過期”包含兩層意思:
(1) 從redis上看,確實沒有設置過期時間,這就保證了,不會出現熱點key過期問題,也就是“物理”不過期。
(2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value里,如果發現要過期了,通過一個后臺的異步線程進行緩存的構建,也就是“邏輯”過期
從實戰看,這種方法對于性能非常友好,唯一不足的就是構建緩存時候,其余線程(非構建緩存的線程)可能訪問的是老數據,但是對于一般的互聯網功能來說這個還是可以忍受。
4. 資源保護:
采用netflix的hystrix,可以做資源的隔離保護主線程池,如果把這個應用到緩存的構建也未嘗不可。