1. 程式人生 > >瞅一瞅快取穿透、快取擊穿和快取雪崩

瞅一瞅快取穿透、快取擊穿和快取雪崩

概念

  1. 快取穿透:正常來說,一個合理設計的快取命中率肯定是在50%以上,如果大量 的去避開快取 ,就會因為 miss cache 造成對DB的負載

  2. 快取擊穿:某一個高流量的 熱點Key,在失效的一瞬間,造成的負載

  3. 快取雪崩:很多Key的失效時間相同 ,快取同一時間大面積的失效,這個時候又來了一波請求,結果請求都懟到資料庫上,從而導致資料庫連線異常

快取雪崩

  1. 給一類快取的失效時間,加上一個隨機值,比如set(key1,value1,time1+random(1,5)) ,避免集體失效。
  2. 雙快取。對於 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
		}
	}

快取擊穿

  1. 互斥鎖,如果快取失效了,則去獲取對應的互斥鎖,如果獲取到了再去訪問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 的過程 。

  1. 採用 非同步更新策略,無論 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等待。

  1. 提供一個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的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;
}