面試經歷
在很長的一段時間里,我以為緩存擊穿和緩存穿透是一個東西,直到最近去騰訊面試,面試官問我緩存擊穿和穿透的區別;我回答它倆是一樣的,面試官馬上抬起頭用他那細長的單眼皮眼睛瞪着我說:“你確定嗎?”,最后面試提醒我,既然有不同的名字,那他們肯定就是不一樣的,也就是說緩存擊穿和緩存穿透不是一個東西;
那么今天我們就看看這倆玩意的區別,以及它們引發的后果;
在項目中加入緩存
一般情況下,我們會把熱點數據放到緩存中,比如常用的字典、用戶信息、訂單詳情等等;也就是說,當項目啟動后,先將熱點數據加載到redis中,以后需要數據時就不用每次都去數據庫查詢了,這樣一來,既減少了數據庫的壓力,也提升了訪問速度,可謂是一舉多得呀!
緩存穿透
緩存穿透是指緩存和數據庫中都沒有的數據,而用戶不斷發起請求,如發起為id為“-1”的數據或id為特別大不存在的數據。這時的用戶很可能是攻擊者,攻擊會導致數據庫壓力過大。
解決方案:
- 接口層增加校驗,如用戶鑒權校驗,id做基礎校驗,id<=0的直接攔截;
- 從緩存取不到的數據,在數據庫中也沒有取到,這時也可以將key-value對寫為key-null,緩存有效時間可以設置短一些,如30秒(設置太長會導致正常情況也沒法使用)。這樣可以防止攻擊用戶反復用同一個id暴力攻擊
緩存擊穿
緩存擊穿指的是大量的key在同一時間過期,但是又有大量的請求需要用到這些已經過期的key,那么程序在redis找不到數據,就會去數據庫里查詢,數據庫處理大量的請求的同時導致壓力瞬間增大,造成壓力過大,甚至導致崩潰;
解決方案
- 設置key值永不過期
- 將key的過期時間設為隨機
- 增加互斥鎖,當多個key過期時,同一時間只有一個查詢請求下發到數據庫,其他的key等待一個個地輪流查,就可以避免數據庫壓力過大的問題;代碼如下:
static Lock lock = new ReentrantLock();
public String getData(String key ) throws InterruptedException {
try {
// 從redis獲取值
String data = getRedisData(key);
// 如果key不存在,從數據庫查詢
if(null == data){
// 嘗試獲取鎖
if(!lock.tryLock()){
// 獲取鎖失敗 ,100ms后在次嘗試
TimeUnit.MILLISECONDS.sleep(100);
data = getData(key);
}
// 走到這里表示成功獲取鎖
// 從myqsl中獲取鎖
data = getMysqlData(key);
// 將數據更新到redis
setDataToRedis(key,value);
}
return data;
} catch (Exception e){
e.printStackTrace();
throw e;
} finally {
// 解鎖
lock.unlock();
}
}
穿透和擊穿的區別
關於穿透和擊穿的區別上面已經介紹的很清楚了,這里在做個總結
- 穿透 :大量請求了緩存和數據庫中都沒有的數據,每次都查詢數據庫,導致數據庫壓力過大
- 擊穿 : 大量key在同一時間過期,導致所有請求都達到數據庫,導致數據庫壓力過大
雪崩效應
雪崩效應指的是由穿透和擊穿引起的數據庫壓力過大,最后導致整個數據庫宕機,一旦數據庫崩了,它所帶來的連鎖反應是可怕的,數據庫不可用的情況下你的服務器也無法使用;這就是雪崩效應;