缓存穿透、缓存并发、缓存失效之思路变迁
來源:https://www.jianshu.com/p/d96906140199
在用緩存的時(shí)候,基本上會(huì)通用遇到以下三個(gè)問題:
-
緩存穿透
-
緩存并發(fā)
-
緩存失效
一、緩存穿透
?
上面三個(gè)圖會(huì)有什么問題呢?
我們?cè)陧?xiàng)目中使用緩存通常都是先檢查緩存中是否存在,如果存在直接返回緩存內(nèi)容,如果不存在就直接查詢數(shù)據(jù)庫然后再緩存查詢結(jié)果返回。這個(gè)時(shí)候如果我們查詢的某一個(gè)數(shù)據(jù)在緩存中一直不存在,就會(huì)造成每一次請(qǐng)求都查詢DB,這樣緩存就失去了意義,在流量大時(shí),可能DB就掛掉了。
那這種問題有什么好辦法解決呢?
要是有人利用不存在的key頻繁攻擊我們的應(yīng)用,這就是漏洞。
有一個(gè)比較巧妙的作法是,可以將這個(gè)不存在的key預(yù)先設(shè)定一個(gè)值。
比如,"key" , “&&”。
在返回這個(gè)&&值的時(shí)候,我們的應(yīng)用就可以認(rèn)為這是不存在的key,那我們的應(yīng)用就可以決定是否繼續(xù)等待繼續(xù)訪問,還是放棄掉這次操作。如果繼續(xù)等待訪問,過一個(gè)時(shí)間輪詢點(diǎn)后,再次請(qǐng)求這個(gè)key,如果取到的值不再是&&,則可以認(rèn)為這時(shí)候key有值了,從而避免了透?jìng)鞯綌?shù)據(jù)庫,從而把大量的類似請(qǐng)求擋在了緩存之中。
二、緩存并發(fā)
有時(shí)候如果網(wǎng)站并發(fā)訪問高,一個(gè)緩存如果失效,可能出現(xiàn)多個(gè)進(jìn)程同時(shí)查詢DB,同時(shí)設(shè)置緩存的情況,如果并發(fā)確實(shí)很大,這也可能造成DB壓力過大,還有緩存頻繁更新的問題。
我現(xiàn)在的想法是對(duì)緩存查詢加鎖,如果KEY不存在,就加鎖,然后查DB入緩存,然后解鎖;其他進(jìn)程如果發(fā)現(xiàn)有鎖就等待,然后等解鎖后返回?cái)?shù)據(jù)或者進(jìn)入DB查詢。
這種情況和剛才說的預(yù)先設(shè)定值問題有些類似,只不過利用鎖的方式,會(huì)造成部分請(qǐng)求等待。
三、緩存失效
引起這個(gè)問題的主要原因還是高并發(fā)的時(shí)候,平時(shí)我們?cè)O(shè)定一個(gè)緩存的過期時(shí)間時(shí),可能有一些會(huì)設(shè)置1分鐘啊,5分鐘這些,并發(fā)很高時(shí)可能會(huì)出在某一個(gè)時(shí)間同時(shí)生成了很多的緩存,并且過期時(shí)間都一樣,這個(gè)時(shí)候就可能引發(fā)一當(dāng)過期時(shí)間到后,這些緩存同時(shí)失效,請(qǐng)求全部轉(zhuǎn)發(fā)到DB,DB可能會(huì)壓力過重。
那如何解決這些問題呢?
其中的一個(gè)簡(jiǎn)單方案就時(shí)講緩存失效時(shí)間分散開,比如我們可以在原有的失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每一個(gè)緩存的過期時(shí)間的重復(fù)率就會(huì)降低,就很難引發(fā)集體失效的事件。
我們討論的第二個(gè)問題時(shí)針對(duì)同一個(gè)緩存,第三個(gè)問題時(shí)針對(duì)很多緩存。
總結(jié)來看:
1、緩存穿透:查詢一個(gè)必然不存在的數(shù)據(jù)。比如文章表,查詢一個(gè)不存在的id,每次都會(huì)訪問DB,如果有人惡意破壞,很可能直接對(duì)DB造成影響。
2、緩存失效:如果緩存集中在一段時(shí)間內(nèi)失效,DB的壓力凸顯。這個(gè)沒有完美解決辦法,但可以分析用戶行為,盡量讓失效時(shí)間點(diǎn)均勻分布。
當(dāng)發(fā)生大量的緩存穿透,例如對(duì)某個(gè)失效的緩存的大并發(fā)訪問就造成了緩存雪崩。
四、提問匯總
1、問題1:
如何解決DB和緩存一致性問題?
答:當(dāng)修改了數(shù)據(jù)庫后,有沒有及時(shí)修改緩存。這種問題,以前有過實(shí)踐,修改數(shù)據(jù)庫成功,而修改緩存失敗的情況,最主要就是緩存服務(wù)器掛了。而因?yàn)榫W(wǎng)絡(luò)問題引起的沒有及時(shí)更新,可以通過重試機(jī)制來解決。而緩存服務(wù)器掛了,請(qǐng)求首先自然也就無法到達(dá),從而直接訪問到數(shù)據(jù)庫。那么我們?cè)谛薷臄?shù)據(jù)庫后,無法修改緩存,這時(shí)候可以將這條數(shù)據(jù)放到數(shù)據(jù)庫中,同時(shí)啟動(dòng)一個(gè)異步任務(wù)定時(shí)去檢測(cè)緩存服務(wù)器是否連接成功,一旦連接成功則從數(shù)據(jù)庫中按順序取出修改數(shù)據(jù),依次進(jìn)行緩存最新值的修改。
2、問題2:
問下緩存穿透那塊!例如,一個(gè)用戶查詢文章,通過ID查詢,按照之前說的,是將緩存的KEY預(yù)先設(shè)置一個(gè)值,,如果通過ID插過來,發(fā)現(xiàn)是預(yù)先設(shè)定的一個(gè)值,比如說是“&&”,那之后的繼續(xù)等待訪問是什么意思,這個(gè)ID什么時(shí)候會(huì)真正被附上用戶所需要的值呢?
答:我剛說的主要是咱們常用的后面配置,前臺(tái)獲取的場(chǎng)景。前臺(tái)無法獲取相應(yīng)的key,則等待,或者放棄。當(dāng)在后臺(tái)配置界面上配置了相關(guān)key和value之后,那么以前的key &&也自然會(huì)被替換掉。你說的那種情況,自然也應(yīng)該會(huì)有一個(gè)進(jìn)程會(huì)在某一個(gè)時(shí)刻,在緩存中設(shè)置這個(gè)ID,再有新的請(qǐng)求到達(dá)的時(shí)候,就會(huì)獲取到最新的ID和value。
3、問題3:
其實(shí)用redis的話,那天看到一個(gè)不錯(cuò)的例子,雙key,有一個(gè)當(dāng)時(shí)生成的一個(gè)附屬key來標(biāo)識(shí)數(shù)據(jù)修改到期時(shí)間,然后快到的時(shí)候去重新加載數(shù)據(jù),如果覺得key多可以把結(jié)束時(shí)間放到主key中,附屬key起到鎖的功能。
答:這種方案,之前我們實(shí)踐過。這種方案會(huì)產(chǎn)生雙份數(shù)據(jù),而且需要同時(shí)控制附屬key與key之間的關(guān)系,操作上有一定復(fù)雜度。
4、問題4:
多級(jí)緩存是什么概念呢?
答:多級(jí)緩存就像我今天之前給大家發(fā)的文章里面提到了,將ehcache與redis做二級(jí)緩存,但同樣會(huì)存在一致性問題,如果我們需要強(qiáng)一致性的話,緩存與數(shù)據(jù)庫同步是會(huì)存在時(shí)間差的,所以我們?cè)诰唧w開發(fā)的過程中,一定要根據(jù)場(chǎng)景來具體分析,二級(jí)緩存更多的解決是,緩存穿透與程序的健壯性,當(dāng)集中式緩存出現(xiàn)問題的時(shí)候,我們的應(yīng)用能夠繼續(xù)運(yùn)行。
總結(jié)
以上是生活随笔為你收集整理的缓存穿透、缓存并发、缓存失效之思路变迁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怼天怼地怼空气的Linus 喜欢和什么样
- 下一篇: Netty 实战:如何编写一个麻小俱全的