緩存穿透沽甥,緩存擊穿,緩存雪崩

緩存穿透:

緩存穿透是指查詢一個一定不存在的數(shù)據(jù)俐填,由于緩存是不命中時被動寫的安接,并且出于容錯考慮,如果從存儲層查不到數(shù)據(jù)則不寫入緩存英融,這將導(dǎo)致這個不存在的數(shù)據(jù)每次請求都要到存儲層去查詢盏檐,失去了緩存的意義。在流量大時驶悟,可能DB就掛掉了胡野,要是有人利用不存在的key頻繁攻擊我們的應(yīng)用,這就是漏洞痕鳍。

解決方案有很多種方法可以有效地解決緩存穿透問題硫豆,最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中笼呆,一個一定不存在的數(shù)據(jù)會被 這個bitmap攔截掉熊响,從而避免了對底層存儲系統(tǒng)的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種)诗赌,如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù) 據(jù)不存在汗茄,還是系統(tǒng)故障),我們?nèi)匀话堰@個空結(jié)果進行緩存铭若,但它的過期時間會很短洪碳,最長不超過五分鐘。

緩存雪崩:

緩存雪崩是指在我們設(shè)置緩存時采用了相同的過期時間叼屠,導(dǎo)致緩存在某一時刻同時失效瞳腌,請求全部轉(zhuǎn)發(fā)到DB,DB瞬時壓力過重雪崩镜雨。

解決方法緩存失效時的雪崩效應(yīng)對底層系統(tǒng)的沖擊非成┦蹋可怕。大多數(shù)系統(tǒng)設(shè)計者考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫荚坞,從而避免失效時大量的并發(fā)請求落到底層存儲系統(tǒng)上挑宠。這里分享一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎(chǔ)上增加一個隨機值西剥,比如1-5分鐘隨機痹栖,這樣每一個緩存的過期時間的重復(fù)率就會降低亿汞,就很難引發(fā)集體失效的事件瞭空。

緩存擊穿:

對于一些設(shè)置了過期時間的key,如果這些key可能會在某些時間點被超高并發(fā)地訪問,是一種非撑匚罚“熱點”的數(shù)據(jù)南捂。這個時候,需要考慮一個問題:緩存被“擊穿”的問題旧找,這個和緩存雪崩的區(qū)別在于這里針對某一key緩存溺健,前者則是很多key。

緩存在某個時間點過期的時候钮蛛,恰好在這個時間點對這個Key有大量的并發(fā)請求過來鞭缭,這些請求發(fā)現(xiàn)緩存過期一般都會從后端DB加載數(shù)據(jù)并回設(shè)到緩存,這個時候大并發(fā)的請求可能會瞬間把后端DB壓垮魏颓。

解決方案

1.使用互斥鎖(mutex key)

業(yè)界比較常用的做法岭辣,是使用mutex。簡單地來說甸饱,就是在緩存失效的時候(判斷拿出來的值為空)沦童,不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key叹话,當操作返回成功時偷遗,再進行l(wèi)oad db的操作并回設(shè)緩存;否則驼壶,就重試整個get緩存的方法氏豌。

SETNX,是「SET if Not eXists」的縮寫辅柴,也就是只有不存在的時候才設(shè)置箩溃,可以利用它來實現(xiàn)鎖的效果。在redis2.6.1之前版本未實現(xiàn)setnx的過期時間碌嘀,所以這里給出兩種版本代碼參考:


//2.6.1前單機版本鎖??

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);????

????}????

??}????

}??

最新版本代碼:

[java]?view plain?copy

public?String?get(key)?{??

??????String?value?=?redis.get(key);??

if?(value?==?null)?{?//代表緩存值過期??

//設(shè)置3min的超時涣旨,防止del操作失敗的時候,下次緩存過期一直不能load?db??

if?(redis.setnx(key_mutex,?1,?3?*?60)?==?1)?{??//代表設(shè)置成功??

???????????????value?=?db.get(key);??

??????????????????????redis.set(key,?value,?expire_secs);??

??????????????????????redis.del(key_mutex);??

}else?{??//這個時候代表同時候的其他線程已經(jīng)load?db并回設(shè)到緩存了股冗,這時候重試獲取緩存值即可??

sleep(50);??

get(key);//重試??

??????????????}??

}else?{??

return?value;????????

??????????}??

?}??

memcache代碼:

[java]?view plain?copy

if?(memcache.get(key)?==?null)?{????

//?3?min?timeout?to?avoid?mutex?holder?crash????

if?(memcache.add(key_mutex,?3?*?60?*?1000)?==?true)?{????

????????value?=?db.get(key);????

????????memcache.set(key,?value);????

????????memcache.delete(key_mutex);????

}else?{????

sleep(50);????

????????retry();????

????}????

}???

2. "提前"使用互斥鎖(mutex key):

在value內(nèi)部設(shè)置1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小霹陡。當從cache讀取到timeout1發(fā)現(xiàn)它已經(jīng)過期時候,馬上延長timeout1并重新設(shè)置到cache止状。然后再從數(shù)據(jù)庫加載數(shù)據(jù)并設(shè)置到cache中烹棉。偽代碼如下:

[java]?view plain?copy

v?=?memcache.get(key);????

if?(v?==?null)?{????

if?(memcache.add(key_mutex,?3?*?60?*?1000)?==?true)?{????

????????value?=?db.get(key);????

????????memcache.set(key,?value);????

????????memcache.delete(key_mutex);????

}else?{????

sleep(50);????

????????retry();????

????}????

}else?{????

if?(v.timeout?<=?now())?{????

if?(memcache.add(key_mutex,?3?*?60?*?1000)?==?true)?{????

//?extend?the?timeout?for?other?threads????

v.timeout?+=3?*?60?*?1000;????

memcache.set(key,?v,?KEY_TIMEOUT?*2);????


//?load?the?latest?value?from?db????

????????????v?=?db.get(key);????

????????????v.timeout?=?KEY_TIMEOUT;????

memcache.set(key,?value,?KEY_TIMEOUT?*2);????

????????????memcache.delete(key_mutex);????

}else?{????

sleep(50);????

????????????retry();????

????????}????

????}????

}???

3. "永遠不過期": ?

這里的“永遠不過期”包含兩層意思:

(1) 從redis上看,確實沒有設(shè)置過期時間怯疤,這就保證了浆洗,不會出現(xiàn)熱點key過期問題,也就是“物理”不過期集峦。

(2) 從功能上看伏社,如果不過期抠刺,那不就成靜態(tài)的了嗎?所以我們把過期時間存在key對應(yīng)的value里摘昌,如果發(fā)現(xiàn)要過期了速妖,通過一個后臺的異步線程進行緩存的構(gòu)建,也就是“邏輯”過期

? ? ? ? 從實戰(zhàn)看聪黎,這種方法對于性能非常友好罕容,唯一不足的就是構(gòu)建緩存時候,其余線程(非構(gòu)建緩存的線程)可能訪問的是老數(shù)據(jù)稿饰,但是對于一般的互聯(lián)網(wǎng)功能來說這個還是可以忍受锦秒。

[java]?view plain?copy

String?get(final?String?key)?{????

????????V?v?=?redis.get(key);????

????????String?value?=?v.getValue();????

long?timeout?=?v.getTimeout();????

if?(v.timeout?<=?System.currentTimeMillis())?{????

//?異步更新后臺異常執(zhí)行????

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;????

}??


4. 資源保護:

采用netflix的hystrix,可以做資源的隔離保護主線程池喉镰,如果把這個應(yīng)用到緩存的構(gòu)建也未嘗不可脂崔。

四種解決方案:沒有最佳只有最合適

解決方案優(yōu)點缺點

簡單分布式互斥鎖(mutex key)?1. 思路簡單

2. 保證一致性

1. 代碼復(fù)雜度增大

2. 存在死鎖的風(fēng)險

3. 存在線程池阻塞的風(fēng)險

“提前”使用互斥鎖?1. 保證一致性同上?

不過期(本文)1. 異步構(gòu)建緩存,不會阻塞線程池1. 不保證一致性梧喷。

2. 代碼復(fù)雜度增大(每個value都要維護一個timekey)砌左。

3. 占用一定的內(nèi)存空間(每個value都要維護一個timekey)。

資源隔離組件hystrix(本文)1. hystrix技術(shù)成熟铺敌,有效保證后端汇歹。

2. hystrix監(jiān)控強大。



1. 部分訪問存在降級策略偿凭。

四種方案來源網(wǎng)絡(luò)产弹,詳文請鏈接:http://carlosfu.iteye.com/blog/2269687?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

總結(jié)

針對業(yè)務(wù)系統(tǒng),永遠都是具體情況具體分析弯囊,沒有最好痰哨,只有最合適。

最后匾嘱,對于緩存系統(tǒng)常見的緩存滿了和數(shù)據(jù)丟失問題斤斧,需要根據(jù)具體業(yè)務(wù)分析,通常我們采用LRU策略處理溢出霎烙,Redis的RDB和AOF持久化策略來保證一定情況下的數(shù)據(jù)安全

詳情 :參考文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撬讽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悬垃,更是在濱河造成了極大的恐慌游昼,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尝蠕,死亡現(xiàn)場離奇詭異烘豌,居然都是意外死亡,警方通過查閱死者的電腦和手機看彼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門廊佩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昧捷,“玉大人,你說我怎么就攤上這事罐寨。” “怎么了序矩?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵鸯绿,是天一觀的道長。 經(jīng)常有香客問我簸淀,道長瓶蝴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任租幕,我火速辦了婚禮舷手,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劲绪。我一直安慰自己男窟,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布贾富。 她就那樣靜靜地躺著歉眷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颤枪。 梳的紋絲不亂的頭發(fā)上汗捡,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音畏纲,去河邊找鬼扇住。 笑死,一個胖子當著我的面吹牛盗胀,可吹牛的內(nèi)容都是我干的艘蹋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼票灰,長吁一口氣:“原來是場噩夢啊……” “哼簿训!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起米间,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤强品,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后屈糊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體的榛,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年逻锐,在試婚紗的時候發(fā)現(xiàn)自己被綠了夫晌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雕薪。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晓淀,靈堂內(nèi)的尸體忽然破棺而出所袁,到底是詐尸還是另有隱情,我是刑警寧澤凶掰,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布燥爷,位于F島的核電站,受9級特大地震影響懦窘,放射性物質(zhì)發(fā)生泄漏前翎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一畅涂、第九天 我趴在偏房一處隱蔽的房頂上張望港华。 院中可真熱鬧,春花似錦午衰、人聲如沸立宜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赘理。三九已至,卻和暖如春扇单,著一層夾襖步出監(jiān)牢的瞬間商模,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工蜘澜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留施流,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓鄙信,卻偏偏與公主長得像瞪醋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子装诡,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容