redis是開發(fā)中重度使用的中間件產(chǎn)品午乓,在使用過程中踩過很多坑,遇到過擊穿闸准,雪崩益愈,穿透等多種場景, 對(duì)上述場景總結(jié)出如下使用經(jīng)驗(yàn)
1 夷家、擊穿
擊穿指的是單個(gè)key在緩存中查不到蒸其,去數(shù)據(jù)庫查詢,這樣如果數(shù)據(jù)量不大或者并發(fā)不大的話是沒有什么問題的库快。
如果數(shù)據(jù)庫數(shù)據(jù)量大并且是高并發(fā)的情況下那么就可能會(huì)造成數(shù)據(jù)庫壓力過大而崩潰.
方案:
1摸袁、設(shè)置永久value ,在業(yè)務(wù)頂峰期定時(shí)去更新數(shù)據(jù)义屏,排隊(duì)刷新緩存靠汁。
2、設(shè)置互斥鎖(mutex key)
比如闽铐,redis的SETNX 去set一個(gè)mutex key蝶怔,當(dāng)操作返回成功時(shí),再進(jìn)行l(wèi)oad db的操作并回設(shè)緩存兄墅;否則踢星,就重試整個(gè)get緩存的方法。
3隙咸、將擊穿的key緩存起來沐悦,但是時(shí)間不能太長,下次進(jìn)來是直接返回不存在五督,但是這種情況無法過濾掉動(dòng)態(tài)的key所踊,就是說每次請(qǐng)求進(jìn)來都是不同額key,這樣還是會(huì)造成這個(gè)問題
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概荷、雪崩
雪崩指的是多個(gè)key查詢并且出現(xiàn)高并發(fā)秕岛,緩存中失效或者查不到,然后都去db查詢,從而導(dǎo)致db壓力突然飆升继薛,從而崩潰修壕。
出現(xiàn)原因:
1、key同時(shí)失效
2遏考、redis本身崩潰了
方案:
1慈鸠、在緩存失效后,通過加鎖或者隊(duì)列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量灌具。比如對(duì)某個(gè)key只允許一個(gè)線程查詢數(shù)據(jù)和寫緩存青团,其他線程等待。
2咖楣、在應(yīng)用層面控制減少查詢的次數(shù)督笆。
3、可以通過緩存reload機(jī)制诱贿,預(yù)先去更新緩存娃肿,再即將發(fā)生大并發(fā)訪問前手動(dòng)觸發(fā)加載緩存。
4珠十、不同的key料扰,設(shè)置不同的過期時(shí)間,具體值可以根據(jù)業(yè)務(wù)決定焙蹭,讓緩存失效的時(shí)間點(diǎn)盡量均勻做二級(jí)緩存晒杈,或者雙緩存策略。A1為原始緩存孔厉,A2為拷貝緩存拯钻,A1失效時(shí),可以訪問A2烟馅,A1緩存失效時(shí)間設(shè)置為短期说庭,A2設(shè)置為長期。(這種方式較復(fù)雜)
方案1和擊穿的第1個(gè)方案類似郑趁,但是這樣是避免不了其它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) {
//從二級(jí)緩存更新數(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)這種情況是捆憎,因?yàn)閻阂忸l繁查詢才會(huì)對(duì)系統(tǒng)造成很大的問題: key緩存并且數(shù)據(jù)庫不存在,所以每次查詢都會(huì)查詢數(shù)據(jù)庫從而導(dǎo)致數(shù)據(jù)庫崩潰梭纹。
布隆過濾器是什么躲惰?
布隆過濾器可以理解為一個(gè)不怎么精確的 set 結(jié)構(gòu),當(dāng)你使用它的 contains 方法判斷某個(gè)對(duì)象是否存在時(shí)变抽,它可能會(huì)誤判础拨。但是布隆過濾器也不是特別不精確氮块,只要參數(shù)設(shè)置的合理,它的精確度可以控制的相對(duì)足夠精確诡宗,只會(huì)有小小的誤判概率滔蝉。
當(dāng)布隆過濾器說某個(gè)值存在時(shí),這個(gè)值可能不存在塔沃;當(dāng)它說不存在時(shí)蝠引,那就肯定不存在。打個(gè)比方蛀柴,當(dāng)它說不認(rèn)識(shí)你時(shí)螃概,肯定就不認(rèn)識(shí);當(dāng)它說見過你時(shí)鸽疾,可能根本就沒見過面吊洼,不過因?yàn)槟愕哪樃J(rèn)識(shí)的人中某臉比較相似 (某些熟臉的系數(shù)組合),所以誤判以前見過你肮韧。
缺點(diǎn):
1融蹂、會(huì)存在一定的誤判率
2旺订、對(duì)新增加的數(shù)據(jù)無法進(jìn)行布隆過濾
3弄企、數(shù)據(jù)的key不會(huì)頻繁的更改
google的 gauva中有布隆過濾的實(shí)現(xiàn)
BloomFilter的關(guān)鍵在于hash算法的設(shè)定和bit數(shù)組的大小確定,通過權(quán)衡得到一個(gè)錯(cuò)誤概率可以接受的結(jié)果区拳。
我們設(shè)置的容錯(cuò)率越小那么過濾函數(shù)也就多拘领,分配的空間也就越大(存放bits),那么誤判率也就越小樱调。
總結(jié):其實(shí)關(guān)于redis擊穿约素,穿透,雪崩基本都是大同小異笆凌,但是想實(shí)現(xiàn)高并發(fā)圣猎,高可用,高可靠的方案乞而,還是要層層阻擋送悔,從最前端的頁面,到ip的限制爪模,到網(wǎng)關(guān)欠啤,再到業(yè)務(wù)過濾和緩存,最好盡可能的減少落到DB的數(shù)據(jù)量屋灌。才能全面保證應(yīng)用程序的高并發(fā)洁段,高可用,高可靠共郭。