redis-緩存穿透,緩存雪崩,緩存擊穿,併發競爭

緩存穿透

定義

緩存穿透是指查詢一個必定不存在的數據,由於緩存中也無該數據的信息,則會直接去數據庫層進行查詢,從系統層面來看像是穿透了緩存層直接達到db,從而稱爲緩存穿透,沒有了緩存層的保護,這種查詢必定不存在的數據對系統來講多是一種危險,若是有人惡意用這種必定不存在的數據來頻繁請求系統,不,準確的說是攻擊系統,請求都會到達數據庫層致使db癱瘓從而引發系統故障。數據庫

解決方案

利用互斥鎖

緩存失效的時候,先去得到鎖,獲得鎖了,再去請求數據庫。沒獲得鎖,則休眠一段時間重試。後端

採用異步更新策略

不管 Key 是否取到值,都直接返回。Value 值中維護一個緩存失效時間,緩存若是過時,異步起一個線程去讀數據庫,更新緩存。須要作緩存預熱(項目啓動前,先加載緩存)操做。緩存

使用布隆過濾器

提供一個能迅速判斷請求是否有效的攔截機制,好比,利用布隆過濾器,內部維護一系列合法有效的 Key。迅速判斷出,請求所攜帶的 Key 是否合法有效。若是不合法,則直接返回。服務器

空置緩存

在第一次查詢完不存在的數據後,將該key與對應的空值也放入緩存中,只不過設定爲較短的失效時間,例如幾分鐘,這樣則能夠應對短期的大量的該key攻擊,設置爲較短的失效時間是由於該值可能業務無關,存在乎義不大,且該次的查詢也未必是攻擊者發起,無太久存儲的必要,故能夠早點失效。併發

緩存雪崩

定義

緩存雪崩是指在咱們設置緩存時採用了相同的過時時間,致使緩存在某一時刻同時失效,請求所有轉發到數據庫,數據庫瞬時壓力太重雪崩。異步

解決方案

給緩存的加一個隨機失效時間

加上一個隨機值,避免集體失效。分佈式

使用互斥鎖

只讓一個線程構建緩存,其餘線程等待構建緩存的線程執行完,從新從緩存獲取數據才能夠,每一個時刻只有一個線程在執行請求,減輕了db的壓力,但缺點也很明顯,下降了系統的qps。高併發

雙緩存策略

咱們有兩個緩存,緩存 A 和緩存 B。緩存 A 的失效時間爲 20 分鐘,緩存 B 不設失效時間。本身作緩存預熱操做。性能

雙緩存細分如下幾個小點:從緩存 A 讀數據,有則直接返回;A 沒有數據,直接從B讀數據,直接返回,而且異步啓動一個更新線程,更新線程同時更新緩存 A 和緩存 B。

緩存擊穿

定義

緩存擊穿其實是緩存雪崩的一個特例.

對於一些設置了過時時間的key,若是這些key可能會在某些時間點被超高併發地訪問,是一種很是「熱點」的數據。這個時候,須要考慮一個問題:緩存被「擊穿」的問題,這個和緩存雪崩的區別在於這裏針對某一key緩存,前者則是不少key。

緩存在某個時間點過時的時候,剛好在這個時間點對這個Key有大量的併發請求過來,這些請求發現緩存過時通常都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。

解決方案

使用互斥鎖

僞代碼示例:
public String get(key) {  
    String value = redis.get(key);  
    if (value == null) { //表明緩存值過時  
        //設置3min的超時,防止del操做失敗的時候,下次緩存過時一直不能load db  
        if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //表明設置成功 
            value = db.get(key);  
            redis.set(key, value, expire_secs);  
            redis.del(key_mutex);  
        } else {  //這個時候表明同時候的其餘線程已經load    db並回設到緩存了,這時候重試獲取緩存值便可  
            sleep(50);  
            get(key);  //重試  
        }  
    } else {  
         return value;        
    }  
 }

"提早"使用互斥鎖

在value內部設置1個超時值(timeout1),timeout1比實際的緩存中的timeout(timeout2)小。當從cache讀取到timeout1發現它已通過期時候,立刻延長timeout1並從新設置到cache。而後再從數據庫加載數據並設置到cache中。

"永遠不過時"

這裏的「永遠不過時」包含兩層意思:

(1) 從redis上看,確實沒有設置過時時間,這就保證了,不會出現熱點key過時問題,也就是「物理」不過時。

(2) 從功能上看,若是不過時,那不就成靜態的了嗎?因此咱們把過時時間存在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;    
}

資源保護

採用netflix的hystrix,能夠作資源的隔離保護主線程池,若是把這個應用到緩存的構建也何嘗不可.

二級緩存

對於熱點數據進行二級緩存,並對於不一樣級別的緩存設定不一樣的失效時間,則請求不會直接擊穿緩存層到達數據庫。

Redis 的併發競爭 Key 問題

所謂 Redis 的併發競爭 Key 的問題也就是多個系統同時對一個 key 進行操做,可是最後執行的順序和咱們指望的順序不一樣,這樣也就致使告終果的不一樣!

須要說明一下,我提早百度了一下,發現答案基本都是推薦用 Redis 事務機制。

我並不推薦使用 Redis 的事務機制。由於咱們的生產環境,基本都是 Redis 集羣環境,作了數據分片操做。

你一個事務中有涉及到多個 Key 操做的時候,這多個Key不必定都存儲在同一個 redis-server 上。所以,Redis 的事務機制,十分雞肋。

若是對這個 Key 操做,不要求順序

這種狀況下,準備一個分佈式鎖,你們去搶鎖,搶到鎖就作 set 操做便可,比較簡單。

若是對這個 Key 操做,要求順序

假設有一個 key1,系統 A 須要將 key1 設置爲 valueA,系統 B 須要將 key1 設置爲 valueB,系統 C 須要將 key1 設置爲 valueC。

指望按照 key1 的 value 值按照 valueA > valueB > valueC 的順序變化。這種時候咱們在數據寫入數據庫的時候,須要保存一個時間戳。

好比

系統A key1 {valueA  3:00}
系統B key1 {valueB  3:05}
系統C key1 {valueC  3:10}

那麼,假設這會系統 B 先搶到鎖,將 key1 設置爲{valueB 3:05}。接下來系統 A 搶到鎖,發現本身的 valueA 的時間戳早於緩存中的時間戳,那就不作 set 操做了,以此類推。

其餘方法,好比利用隊列,將 set方法變成串行訪問也能夠。總之,靈活變通。

緩存預熱

新的緩存系統沒有任何緩存數據,在緩存重建數據的過程當中,系統性能和數據庫負載都不太好,因此最好是在系統上線以前就把要緩存的熱點數據加載到緩存中,這種緩存預加載手段就是緩存預熱。
解決思路:

  • 一、直接寫個緩存刷新頁面,上線時手工操做下;
  • 二、數據量不大,能夠在項目啓動的時候自動進行加載;
  • 三、定時刷新緩存;

緩存熱備

緩存熱備即當一臺緩存服務器不可用時能實時切換到備用緩存服務器,不影響緩存使用。集羣模式下,每一個主節點都會有一個或多個從節點來當備用,一旦主節點掛點,從節點當即充當主節點使用。

相關文章
相關標籤/搜索