1. 程式人生 > >緩存穿透 & 緩存雪崩 & 緩存擊穿

緩存穿透 & 緩存雪崩 & 緩存擊穿

失效 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  }  

緩存穿透 & 緩存雪崩 & 緩存擊穿