redis是開發(fā)中重度使用的中間件產品,在使用過程中踩過很多坑,遇到過擊穿,雪崩,穿透等多種場景, 對上述場景總結出如下使用經(jīng)驗
1 、擊穿
擊穿指的是單個key在緩存中查不到,去數(shù)據(jù)庫查詢,這樣如果數(shù)據(jù)量不大或者并發(fā)不大的話是沒有什么問題的。
如果數(shù)據(jù)庫數(shù)據(jù)量大并且是高并發(fā)的情況下那么就可能會造成數(shù)據(jù)庫壓力過大而崩潰.
方案:
1、設置永久value ,在業(yè)務頂峰期定時去更新數(shù)據(jù),排隊刷新緩存。
2、設置互斥鎖(mutex key)
比如,redis的SETNX 去set一個mutex key,當操作返回成功時,再進行l(wèi)oad db的操作并回設緩存;否則,就重試整個get緩存的方法。
3、將擊穿的key緩存起來,但是時間不能太長,下次進來是直接返回不存在,但是這種情況無法過濾掉動態(tài)的key,就是說每次請求進來都是不同額key,這樣還是會造成這個問題
public function test($key){
$key_mutex = "key_mutex";
$redis = new \Redis;
$val = $redis->get($key);
if($val == null){
if($redis->setnx($key_mutex,"DB or function")){
$redis->set($key,"DB or function");
$redis->del($key_mutex);
return "DB or function";
}else{
return $redis->get($key);
}
} else {
return $val;
}
}
2、雪崩
雪崩指的是多個key查詢并且出現(xiàn)高并發(fā),緩存中失效或者查不到,然后都去db查詢,從而導致db壓力突然飆升,從而崩潰。
出現(xiàn)原因:
1、key同時失效
2、redis本身崩潰了
方案:
1、在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
2、在應用層面控制減少查詢的次數(shù)。
3、可以通過緩存reload機制,預先去更新緩存,再即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存。
4、不同的key,設置不同的過期時間,具體值可以根據(jù)業(yè)務決定,讓緩存失效的時間點盡量均勻做二級緩存,或者雙緩存策略。A1為原始緩存,A2為拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置為短期,A2設置為長期。(這種方式較復雜)
方案1和擊穿的第1個方案類似,但是這樣是避免不了其它key去查數(shù)據(jù)庫,只能通過方案2 減少查詢的次數(shù)。
public static function sget($key, $obtainCacheDataFunc = NULL, $expiry = 0)
{
$redis = new \Redis;
$cacheData = $redis->get($key);
if ($redis->exists($key) == 0) {
//從二級緩存更新數(shù)據(jù)
$cacheData = $redis->get("getSecondKey");
if (is_callable($obtainCacheDataFunc)) {
$lockGetCacheKey = "getTaskKey";
if ($redis->set($lockGetCacheKey, 'key is exist?', 'ex', 60, 'nx')) {
$cacheData = serialize($obtainCacheDataFunc($key));
if (!$redis->set($key, $cacheData, 'ex', $expiry)) Log::write('redis_set_error');
$redis->set("getSecondKey", $cacheData, 'ex', $expiry + 3600);
$redis->del([$lockGetCacheKey]);
}
}
}
return unserialize($cacheData);
}
3、穿透
一般是出現(xiàn)這種情況是,因為惡意頻繁查詢才會對系統(tǒng)造成很大的問題: key緩存并且數(shù)據(jù)庫不存在,所以每次查詢都會查詢數(shù)據(jù)庫從而導致數(shù)據(jù)庫崩潰。
布隆過濾器是什么?
布隆過濾器可以理解為一個不怎么精確的 set 結構,當你使用它的 contains 方法判斷某個對象是否存在時,它可能會誤判。但是布隆過濾器也不是特別不精確,只要參數(shù)設置的合理,它的精確度可以控制的相對足夠精確,只會有小小的誤判概率。
當布隆過濾器說某個值存在時,這個值可能不存在;當它說不存在時,那就肯定不存在。打個比方,當它說不認識你時,肯定就不認識;當它說見過你時,可能根本就沒見過面,不過因為你的臉跟它認識的人中某臉比較相似 (某些熟臉的系數(shù)組合),所以誤判以前見過你。
缺點:
1、會存在一定的誤判率
2、對新增加的數(shù)據(jù)無法進行布隆過濾
3、數(shù)據(jù)的key不會頻繁的更改
google的 gauva中有布隆過濾的實現(xiàn)
BloomFilter的關鍵在于hash算法的設定和bit數(shù)組的大小確定,通過權衡得到一個錯誤概率可以接受的結果。
我們設置的容錯率越小那么過濾函數(shù)也就多,分配的空間也就越大(存放bits),那么誤判率也就越小。
總結:其實關于redis擊穿,穿透,雪崩基本都是大同小異,但是想實現(xiàn)高并發(fā),高可用,高可靠的方案,還是要層層阻擋,從最前端的頁面,到ip的限制,到網(wǎng)關,再到業(yè)務過濾和緩存,最好盡可能的減少落到DB的數(shù)據(jù)量。才能全面保證應用程序的高并發(fā),高可用,高可靠。