瞅一瞅快取穿透、快取擊穿和快取雪崩
阿新 • • 發佈:2018-11-20
概念
-
快取穿透
:正常來說,一個合理設計的快取命中率肯定是在50%以上,如果大量
的去避開快取 ,就會因為miss cache
造成對DB的負載 -
快取擊穿
:某一個
高流量的熱點Key
,在失效的一瞬間,造成的負載 -
快取雪崩
:很多Key的失效時間相同 ,快取同一時間大面積的失效,這個時候又來了一波請求,結果請求都懟到資料庫上,從而導致資料庫連線異常
快取雪崩
- 給一類快取的失效時間,加上一個隨機值,比如
set(key1,value1,time1+random(1,5))
,避免集體失效。 - 雙快取。對於
Value1
做兩級快取,Key1
Key2
,獲取資料流程如下:
/** *獲取2級快取 **/ function getKeyDouble(Key1){ value = redis->get(Key1) //如果Key1快取失效 if(!value){ Key2 = getKeyD(Key1) value = redis->get(Key2) //非同步 set Key1,Key2 DbValue = DB.get(sql) ThreadSetKey(Key1,DbValue) ThreadSetKey(Key2,DbValue) }else{ return value } }
快取擊穿
- 互斥鎖,如果快取失效了,則去獲取對應的互斥鎖,如果獲取到了再去訪問DB,如果沒有就短暫休眠,然後重試,或者返回NUll 讓使用者重新整理
set nx 是原子命令,不存在 set 的併發同時設定的問題
在請求 Key 的時候 ,Get 某個熱點 Key 做如下步驟來保證 不被快取擊穿的問題 1 先 Get Redis Key ,返回 True 則正常返回快取 2 如果 1 返回 False 則設定一個 key_mutex 作為 Key 的互斥鎖(保證執行緒的Lock狀態),,如果設定成功,代表互斥鎖可寫,在這個執行緒中去 DB Loading 新的快取,設定成功之後,刪除互斥鎖 3 如果2 setnx 失敗,代表互斥鎖處於Lock狀態,執行緒等待一定的時間,這裡代表Key在寫入的階段,按照業務邏輯做處理,可以選擇,遞迴的繼續獲取Key方法;短暫的返回Null ,伺服器繁忙請重新整理重試等 String get(String key) { String value = redis.get(key); if (value == null) { if (redis.setnx(key_mutex, "1")) { // 3 min timeout to avoid mutex holder crash redis.expire(key_mutex, 3 * 60) value = db.get(key); redis.set(key, value); redis.delete(key_mutex); } else { //其他執行緒休息50毫秒後重試 Thread.sleep(50); get(key); } } return value; }
這種解決辦法如果在 Db.get + set
出問題導致釋放鎖失敗,就會存在 死鎖
的風險,使用者需要等待,會有 loading
的過程 。
- 採用
非同步更新
策略,無論 key 是否取到值,都直接返回。value值中維護一個快取失效時間,快取如果過期,非同步起一個執行緒去讀資料庫,更新快取。需要做快取預熱(專案啟動前,先載入快取)操作。
String get(final String key) {
V v = redis.get(key);
String value = v.getValue();
long timeout = v.getTimeout();
if (v.timeout <= System.currentTimeMillis()) {
// 非同步更新後臺異常執行
threadPool.execute(new Runnable() {
public void run() {
String keyMutex = "mutex:" + key;
if (redis.setnx(keyMutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(keyMutex, 3 * 60);
String dbValue = db.get(key);
redis.set(key, dbValue);
redis.delete(keyMutex);
}
}
});
}
return value;
}
相當於 在上面(1)互斥鎖的基礎上加上了一個 維護的過期時間,如果過期就做 快取預熱
,提前使用互斥鎖 ,好處就是使用者沒有 loading等待。
- 提供一個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的key。迅速判斷出,請求所攜帶的Key是否合法有效。如果不合法,則直接返回。
布隆過濾器 來判斷請求的key 是否在 keys 列表中,如果不在那麼就進入了 處理流程,而不是對DB 進行衝擊 。
// 快取穿透的應對方案
String get(String key) {
String value = redis.get(key);
if (value == null) {
//沒有命中快取,並且key不存在
if (!bloomfilter.mightContain(key)) {
return null;
} else {
value = db.get(key);
redis.set(key,value);
}
}
return value;
}