緩存穿透 & 緩存雪崩 & 緩存擊穿
阿新 • • 發佈:2018-01-11
失效 2.6 加載數據 div 註意 成功 memcach val 加鎖
一 緩存穿透
1. 行為
查詢一個一定不存在的數據。存儲層(姑且認為是db,下面都用db指代)查不到數據則不寫入緩存,那麽下次請求這個不存在的數據同樣會到db層查詢,失去了緩存的意義。流量大或人為惡意攻擊可能會使db宕掉。
2. 解決方案
(1) 布隆過濾器。將全量可能存在的數據哈希到一個足夠大的bitmap中,布隆可能誤報,但絕不會漏報,那麽一定不存在的數據會被攔截掉,從而緩解了對db的壓力
(2) 空結果也進入緩存。如果查詢返回的結果為空 (數據不存在 | 服務不可用), 仍將數據-空結果進行緩存,註意將其過期時間設置非常短(不超過5min)
二 緩存雪崩
1. 行為
設置緩存時采用了相同的過期時間,導致緩存在某一時刻同時實效,請求全部打到db,db瞬間壓力過重雪崩。
2. 解決方案
(1) 加鎖或采用隊列保證緩存的單線程,避免失效時大量請求落到db存儲系統
(2) 緩存時間離散化。在願緩存的失效時間基礎上增加一個隨機值,降低同一時間集體失效概率
三 緩存擊穿
1. 行為
對於設置了過期時間的某些key,在過期的時間點,恰好對這個key有大量的並發請求過來,這些請求發現緩存過期同時請求db加載數據並回設到緩存,這個高並發的請求可能瞬間把後端db壓垮。
2.解決方案
(1) 永不過期
(2) 使用互斥鎖。緩存失效的時候,不直接load db,而是使用緩存工具中帶有成功返回標識的方法(比如redis的setnx,memcache的add)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設緩存。否則重試整個get緩存的方法。
在redis2.6.1之前版本未實現setnx的過期時間,所以給出兩種版本代碼參考
a) setnx無過期時間版本:
1 String get(String key) { 2 String value = redis.get(key); 3 if (value == null) { 4 if (redis.setnx(key_mutex, "1")) { 5 // 3 min timeout to avoid mutex holder crash 6 redis.expire(key_mutex, 3 * 60)7 value = db.get(key); 8 redis.set(key, value); 9 redis.delete(key_mutex); 10 } else { 11 //其他線程休息50毫秒後重試 12 Thread.sleep(50); 13 get(key); 14 } 15 } 16 }
b) redis2.6.1後, setnx有過期時間版本:
1 public String get(key) { 2 String value = redis.get(key); 3 if (value == null) { //代表緩存值過期 4 //設置3min的超時,防止del操作失敗的時候,下次緩存過期一直不能load db 5 if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表設置成功 6 value = db.get(key); 7 redis.set(key, value, expire_secs); 8 redis.del(key_mutex); 9 } else { //這個時候代表同時候的其他線程已經load db並回設到緩存了,這時候重試獲取緩存值即可 10 sleep(50); 11 get(key); //重試 12 } 13 } else { 14 return value; 15 } 16 }
緩存穿透 & 緩存雪崩 & 緩存擊穿