Redis 快取擊穿、穿透、雪崩的原因以及解決方案

xingkong12138發表於2020-09-16

更多文章請檢視我的部落格

前因


最近搞了個redis作為記錄一些頻發請求以及一些經常訪問,但是訪問頁面的資料量較大的頁面。剛開始的時候沒有什麼問題,但是當運營到一段時間後,發現了些問題,經過查閱資料,解決了問題。所以在這裡記錄下。

快取穿透


原因描述–快取穿透

指查詢一個資料庫一定不存在的資料。正常的使用快取流程大致是,資料查詢先進行快取查詢,如果key不存在或者key已經過期,再對資料庫進行查詢,並把查詢到的物件,放進快取。如果資料庫查詢物件為空,則不放進快取。

但是這種方法存在一個問題,比如我傳一個使用者id為-1,這個使用者id在快取裡面是肯定不存在的,所以會去資料庫裡面查詢,如果有搞事情的人,大批次請求並傳使用者id為-1,那就和沒用redis一樣,導致資料庫壓力過大而崩潰。

解決方法–快取穿透

方法一:在介面層增加校驗,不合法的引數直接返回。不相信任務呼叫方,根據自己提供的API介面規範來,作為被呼叫方,要考慮可能任何的引數傳值。

方法二:在快取查不到,DB中也沒有的情況,可以將對應的key的value寫為null,或者其他特殊值寫入快取,同時將過期失效時間設定短一點,以免影響正常情況。這樣是可以防止反覆用同一個ID來暴力攻擊。

方法三:正常使用者是不會這樣暴力功擊,只有是惡意者才會這樣做,可以在閘道器NG作一個配置項,為每一個IP設定訪問閾值。

方法四:高階使用者布隆過濾器(Bloom Filter),這個也能很好地防止快取穿透。原理就是利用高效的資料結構和演算法快速判斷出你這個Key是否在DB中存在,不存在你return就好了,存在你就去查了DB重新整理KV再return。

快取雪崩


原因描述–快取雪崩

在同一個時間,快取大批次的失效,然後所有請求都打到DB資料庫,導致DB資料庫直接扛不住崩了。

比如,電商首頁快取,如果首頁的key全部都在某一時刻失效,剛好在那一時刻有秒殺活動,那這樣的話就所有的請求都被打到了DB。併發大的情況下DB必然扛不住,沒有其他降級之類的方案的話,DBA也只能重啟DB,但是這樣又會被新的流量搞掛。

解決方法–快取雪崩

批次往redis存資料的時候,把每個key的失效時間加上個隨機數,比如1-5分鐘隨機,這樣的話就能保證資料不會在同一個時間大面積失效。

快取擊穿


原因描述–快取擊穿

快取擊穿跟快取雪崩有些類似,雪崩是大面積快取失效,導致資料庫崩潰,而快取擊穿是一個key是熱點,不停地扛住大併發請求,全都集中訪問此key,而當此key過期瞬間,持續的大併發就擊穿快取,全都打在DB上。就又引發雪崩的問題。

解決方法–快取擊穿

方法一:把這個熱點key設定為永久有效。

方法二:使用互斥鎖,這是比較常用的方法,簡單地來說,就是在快取失效的時候(判斷拿出來的值為空),不是立即去查詢資料庫,而是先使用快取工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行查詢資料庫的操作並回設快取;否則,就重試整個get快取的方法。

互斥程式碼:

function get($key){
    $value = $redis->get($key);
    if($value == null){
        //不存在,設定3min的超時,防止del操作失敗的時候,下次快取過期一直不能查詢資料庫
        if ($redis->setnx("key_mutex", 1, 3 * 60) == 1){
            $value = "";//這是查詢資料庫檔案
            $redis->set(key, value, expire_secs);
            $redis->del(key_mutex);
        }else{
            //這個時候代表同時候的其他執行緒已經查詢資料庫並回設到快取了,這時候重試獲取快取值即可
            sleep(50);
            get(key);  //重試
        }
    }else{
        //存在則直接返回
        return $value;
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結
我的部落格:www.zhangkaixing.com

相關文章