干貨!教你深入理解緩存問題,使用緩存無壓力!
在高并發(fā)或大數(shù)據(jù)的場景下,面對熱點(diǎn)數(shù)據(jù)的使用性能問題,緩存是常用的解決方式,主要作用包括將數(shù)據(jù)寫入速度更快的存儲(chǔ)設(shè)備;將數(shù)據(jù)緩存到離應(yīng)用最近的位置;將數(shù)據(jù)緩存到離用戶最近的位置等。
一、緩存雪崩
緩存雪崩可簡單理解為,由于原有緩存失效,新緩存未到期間(例如:我們設(shè)置緩存時(shí)采用了相同的過期時(shí)間,在同一時(shí)刻出現(xiàn)大面積的緩存過期),所有原本應(yīng)該訪問緩存的請求都去查詢數(shù)據(jù)庫,對數(shù)據(jù)庫CPU和內(nèi)存造成巨大壓力,嚴(yán)重的會(huì)導(dǎo)致數(shù)據(jù)庫宕機(jī),從而形成一系列連鎖反應(yīng),造成整個(gè)系統(tǒng)崩潰。
緩存失效時(shí)的雪崩效應(yīng)對底層系統(tǒng)的沖擊很大,大多系統(tǒng)設(shè)計(jì)者采用加鎖或隊(duì)列的方式來保證不會(huì)有大量的線程對數(shù)據(jù)庫一次性進(jìn)行讀寫,從而避免失效時(shí)大量并發(fā)請求落到底層存儲(chǔ)系統(tǒng)上。另外一個(gè)簡單方案就是分散緩存失效時(shí)間,例如可以在原有的失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每一個(gè)緩存過期時(shí)間的重復(fù)率就會(huì)降低,很難引發(fā)集體失效的事件。
1.若一般并發(fā)量不是特別多,常用解決方案是加鎖排隊(duì),可減輕數(shù)據(jù)庫的壓力,但不會(huì)提高系統(tǒng)吞吐量。假設(shè)在高并發(fā)下,緩存重建期間key是鎖著的,此時(shí)1000個(gè)請求中999個(gè)在阻塞狀態(tài),同樣會(huì)導(dǎo)致用戶等待超時(shí),這種方法治標(biāo)不治本!
注意:使用加鎖排隊(duì)的方式解決分布式環(huán)境的并發(fā)問題,可能還要解決分布式鎖的問題;線程還會(huì)被阻塞,用戶體驗(yàn)較差,因此,在真正的高并發(fā)場景下很少使用!
2.另一個(gè)解決方案是,給每一個(gè)緩存數(shù)據(jù)增加相應(yīng)的緩存標(biāo)記,記錄緩存是否失效,如果緩存標(biāo)記失效,則更新數(shù)據(jù)緩存。
解釋說明
1.緩存標(biāo)記:記錄緩存數(shù)據(jù)是否過期,若過期會(huì)觸發(fā)通知另外的線程,則在后臺(tái)更新實(shí)際key的緩存;
2.緩存數(shù)據(jù):它的過期時(shí)間比緩存標(biāo)記的時(shí)間延長1倍,例:標(biāo)記緩存時(shí)間30分鐘,數(shù)據(jù)緩存設(shè)置為60分鐘。 這樣,當(dāng)緩存標(biāo)記key過期后,實(shí)際緩存還能將舊數(shù)據(jù)返回給調(diào)用端,直到另外的線程在后臺(tái)更新完成后,才會(huì)返回新緩存。
關(guān)于緩存崩潰的三種解決方案:使用鎖或隊(duì)列、設(shè)置過期標(biāo)志更新緩存、為key設(shè)置不同的緩存失效時(shí)間,還有一種被稱為“二級緩存”的解決方法,有興趣的小伙伴可自行研究。
二、緩存穿透
緩存穿透是指用戶無法在緩存中查詢數(shù)據(jù),需要去數(shù)據(jù)庫查詢,再返回空(相當(dāng)于進(jìn)行兩次無用查詢)。此時(shí)請求就繞過緩存直接查數(shù)據(jù)庫,這就是經(jīng)常提到的緩存命中率問題。
有多種方法可有效的解決緩存穿透問題,最常見的是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一定不存在的數(shù)據(jù)則會(huì)被bitmap攔截掉,從而避免對底層存儲(chǔ)系統(tǒng)的查詢壓力。
另外更為簡單的方法是,若一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀粚⒋丝战Y(jié)果進(jìn)行緩存,過期時(shí)間會(huì)很短,最長不超過五分鐘。通過這個(gè)直接設(shè)置的默認(rèn)值存放到緩存,這樣第二次到緩沖中獲取就有值了,而不會(huì)繼續(xù)訪問數(shù)據(jù)庫,這種辦法最為簡單。
將空結(jié)果進(jìn)行緩存,下次同樣的請求則直接返回空,即可避免當(dāng)查詢的值為空時(shí)引起的緩存穿透。同時(shí)也可單獨(dú)設(shè)置一個(gè)緩存區(qū)域存儲(chǔ)空值,對要查詢的key進(jìn)行預(yù)先校驗(yàn),然后再放行給后面的正常緩存處理邏輯。
三、緩存預(yù)熱
緩存預(yù)熱就是系統(tǒng)上線后,將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)中。這樣即可避免在用戶請求時(shí),先查詢數(shù)據(jù)庫,再將數(shù)據(jù)緩存的問題,用戶可直接查詢事先被預(yù)熱的緩存數(shù)據(jù)。
解決思路:
1.直接寫一個(gè)緩存刷新頁面,上線時(shí)手工操作;
2.數(shù)據(jù)量不大時(shí),可以在項(xiàng)目啟動(dòng)時(shí)自動(dòng)進(jìn)行加載;
3.定時(shí)刷新緩存。
四、緩存更新
除了緩存服務(wù)器自帶的緩存失效策略外(Redis默認(rèn)的有6種策略可供選擇),還可根據(jù)具體的業(yè)務(wù)需求進(jìn)行自定義緩存淘汰,常見策略有:
1.定期清理過期緩存;
2.當(dāng)有用戶請求時(shí),判斷緩存是否過期,過期則在底層系統(tǒng)得到新數(shù)據(jù)并更新緩存。
兩者各有優(yōu)劣,第一種的缺點(diǎn)是維護(hù)大量緩存的key較為麻煩,第二種的缺點(diǎn)是每次用戶請求都需判斷緩存是否失效,邏輯相對比較復(fù)雜。具體選擇哪種方案,可根據(jù)應(yīng)用場景權(quán)衡。
五、緩存降級
當(dāng)訪問量劇增、服務(wù)出現(xiàn)問題(如響應(yīng)時(shí)間慢或不響應(yīng))或非核心服務(wù)影響到核心流程的性能時(shí),仍需保證服務(wù)可用,即使是有損服務(wù)。系統(tǒng)可以根據(jù)一些關(guān)鍵數(shù)據(jù)進(jìn)行自動(dòng)降級,也可配置開關(guān)實(shí)現(xiàn)人工降級。
降級的最終目的是保證核心服務(wù)可用,即使是有損的,而且有些服務(wù)是無法降級的(如加入購物車、結(jié)算)。
在進(jìn)行降級之前要對系統(tǒng)進(jìn)行梳理,確認(rèn)哪些需要保護(hù),哪些可降級;比如可參考日志級別設(shè)置預(yù)案:
1.一般:有些服務(wù)偶爾因網(wǎng)絡(luò)抖動(dòng)或者服務(wù)正在上線而超時(shí),可自動(dòng)降級;
2.警告:有些服務(wù)在一段時(shí)間內(nèi)成功率有波動(dòng)(如在95~100%之間),可自動(dòng)降級或人工降級,并發(fā)送警告;
3.錯(cuò)誤:可用率低于90%,或數(shù)據(jù)庫連接池被耗盡,或訪問量猛增到系統(tǒng)能承受的最大閥值,此時(shí)可根據(jù)情況自動(dòng)降級或者人工降級;
4.嚴(yán)重錯(cuò)誤:因?yàn)樘厥庠驍?shù)據(jù)錯(cuò)誤,此時(shí)需要緊急人工降級。