緩存的三個問題

緩存的做用是在內存中臨時存儲來自外部系統(如數據庫)的數據,以便讓請求更快的獲得響應。若是請求數據在緩存中不存在,或者已經超時失效,那麼也要從外部系統查詢,而後放入緩存中,這個過程叫刷新緩存。數據庫

這是緩存的基本使用邏輯,可是實際當中可能出現三種異常狀況,它們會致使緩存起不到預期的使用效果,以致於系統性能明顯降低。緩存

緩存命中率太低

緩存命中率指的是從緩存中找到數據的請求佔全部請求的比重。例如 100 個請求當中有 90 個請求的結果能夠直接從緩存中得到,那麼命中率就是 90%。剩下 10% 的請求就要從外部系統查詢數據,填入緩存,而後再返回。分佈式

什麼狀況下緩存命中率高呢?請求的數據比較集中的時候,例如 80% 的請求集中在 20% 的數據上,這部分數據也被稱做熱點之類的。熱點越熱,緩存命中率越高。性能

所以之因此出現緩存命中率太低,天然就是由於熱點不夠熱,請求的數據很是分散。命中率太低的後果就是不少請求的數據仍需從外部系統查詢,假如是數據庫的話,數據庫的壓力就會很是大,同時系統的響應也明顯變慢。線程

要緩解緩存命中率太低的問題,最直接的辦法固然是加大緩存。本地緩存不夠,就用分佈式緩存,多臺機器分開存儲。設計

特例一:分散攻擊

有時候系統正常狀況下是存在熱點數據的,但忽然有一天出現大量的分散請求,致使緩存命中率直線降低。這些異常的請求能夠看做是有意的攻擊行爲,目的就是讓系統沒法響應。內存

而遇到攻擊行爲的話,加大緩存多是徒勞的,這時候須要去識別請求,對於被歸類爲攻擊的請求主動延長響應時間,甚至拒絕返回結果。同步

好比說一個論壇,忽然遇到大量請求,均勻的訪問五年內的帖子內容,致使數據庫負載很大,此時能夠將訪問老帖子的請求(帖子ID一般是遞增的,ID越小表示發帖時間越久)返回時間適當延長,好比延長到五分鐘。不過使用這種作法時千萬不要簡單的暫停線程,這會致使沒有多餘的線程來處理正常的請求。基礎

特例二:無效的 key

有時候系統收到大量請求,這些請求的數據非但緩存中沒有,連數據庫也沒有,那麼每一個請求不但由於緩存未命中而去查詢數據庫,並且由於數據庫沒有記錄而沒法填充到緩存。這是更加惡劣的狀況。請求

遇到這種狀況,一樣須要鑑別無效的請求。對於 key 自增的狀況,能夠經過值範圍來鑑別;而對於使用 UUID 的狀況,就須要低成本的鑑別方式,布隆過濾器是一個選擇。

大量緩存項同時刷新

緩存一般都是存在失效時間的,須要避免的一種狀況就是大量緩存項在同一個時間點失效,若是此時對這些數據的請求量大,那麼這些請求就會同時去刷新各自的緩存,這就將壓力傳遞到了外部系統上。避免這種狀況的辦法就是在預約的失效時間基礎上加上一個隨機值,以錯開緩存項的失效時間。

大量請求刷新同一個緩存項

一個請求遇到緩存失效,因而去刷新緩存,而在這個過程當中又有大量請求來訪問正在刷新的緩存項,致使該緩存項完成本次刷新後,又馬上被另外一個線程刷新,實質上每一個請求都由於緩存未命中而去訪問了外部系統。

出現這個現象的緣由是設計上的不合理。當一個緩存正在刷新時,訪問該緩存項的其餘線程應該等待刷新完畢,這樣它們就能夠直接從緩存得到結果了。線程同步固然是用鎖。若是是分佈式系統,那就用分佈式鎖。

相關文章
相關標籤/搜索