高併發總結—緩存穿透、併發、失效、預熱、雪崩、淘汰算法

一、緩存穿透

我們在項目中使用緩存通常都是先檢查緩存中是否存在,如果存在直接返回緩存內容,如果不存在就直接查詢數據庫然後再緩存查詢結果返回。這個時候如果我們查詢的某一個數據在緩存中一直不存在,就會造成每一次請求都查詢DB,這樣緩存就失去了意義,在流量大時,可能DB就掛掉了。

那這種問題有什麼好辦法解決呢?要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。 有一個比較巧妙的作法是,可以將這個不存在的key預先設定一個值。 比如,"**" , "&&","NullObject". 在返回這個&&值的時候,我們的應用就可以認為這是不存在的key,那我們的應用就可以決定是否繼續等待繼續訪問,還是放棄掉這次操作。如果繼續等待訪問,過一個時間輪詢點後,再次請求這個key,如果取到的值不再是&&,則可以認為這時候key有值了,從而避免了透傳到數據庫,從而把大量的類似請求擋在了緩存之中。

二、緩存併發

有時候如果網站併發訪問高,一個緩存如果失效,可能出現多個進程同時查詢DB,同時設置緩存的情況,如果併發確實很大,這也可能造成DB壓力過大,還有緩存頻繁更新的問題。

有一個比較巧妙的作法是對緩存查詢加鎖,如果KEY不存在,就加鎖,然後查DB入緩存,然後解鎖;其他進程如果發現有鎖就等待,然後等解鎖後返回數據或者進入DB查詢。

這種情況和剛才說的預先設定值問題有些類似,只不過利用鎖的方式,會造成請求等待,效率低。

三、緩存失效

引起這個問題的主要原因還是高併發的時候,平時我們設定一個緩存的過期時間時,可能有一些會設置1分鐘啊,5分鐘這些,併發很高時可能會出在某一個時間同時生成了很多的緩存,並且過期時間都一樣,這個時候就可能引發一當過期時間到後,這些緩存同時失效,請求全部轉發到DB,DB可能會壓力過重。

那如何解決這些問題呢? 其中的一個簡單方案就時將緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件。

四、緩存雪崩

緩存雪崩可能是因為數據未加載到緩存中,或者緩存同一時間大面積的失效,從而導致所有請求都去查數據庫,導致數據庫CPU和內存負載過高,甚至宕機。

解決思路:

1,採用加鎖計數

,或者使用合理的隊列數量來避免緩存失效時對數據庫造成太大的壓力。這種辦法雖然能緩解數據庫的壓力,但是同時又降低了系統的吞吐量。

2,分析用戶行為,儘量讓失效時間點均勻分佈。避免緩存雪崩的出現。

3,如果是因為某臺緩存服務器宕機,可以考慮做主備,比如:redis主備,但是雙緩存涉及到更新事務的問題,update可能讀到髒數據,需要好好解決。

五、緩存預熱

單機web系統情況下比較簡單。

解決思路:

1,直接寫個緩存刷新頁面,上線時手工操作下。

2,數據量不大,可以在WEB系統啟動的時候加載。

3,搞個定時器定時刷新緩存,或者由用戶觸發都行。

分佈式緩存系統,如Memcached,Redis,比如緩存系統比較大,由十幾臺甚至幾十臺機器組成,這樣預熱會複雜一些。

解決思路:

1,寫個程序去跑。

2,單個緩存預熱框架。

緩存預熱的目標就是在系統上線前,將數據加載到緩存中。

六、緩存算法

FIFO算法:First in First out,先進先出。原則:一個數據最先進入緩存中,則應該最早淘汰掉。也就是說,當緩存滿的時候,應當把最先進入緩存的數據給淘汰掉。LFU算法:Least Frequently Used,最不經常使用算法。LRU算法:Least Recently Used,近期最少使用算法。

LRU和LFU的區別。LFU算法是根據在一段時間裡數據項被使用的次數選擇出最少使用的數據項,即根據使用次數的差異來決定。而LRU是根據使用時間的差異來決定的。


分享到:


相關文章: