天天看點

4種常見的緩存問題及解決方案詳解前言緩存穿透緩存擊穿緩存雪崩雙寫不一緻最後

前言

使用緩存可以緩解大流量壓力,顯著提高程式的性能。我們在使用緩存系統時,尤其是大并發情況下,經常會遇到一些“疑難雜症”。本文總結了一些使用緩存時常見的問題及解決方案,以後在遇到這類問題時可以作為參考,在設計緩存系統的時候也應該考慮這些常見的情況。

為了表述友善,本文以資料庫查詢緩存為例,使用緩存可以減小對資料庫的壓力。

4種常見的緩存問題及解決方案詳解前言緩存穿透緩存擊穿緩存雪崩雙寫不一緻最後

緩存穿透

我們在使用緩存時,往往先嘗試去緩存中取值,如果沒有,再去資料庫取值,如果資料庫也沒有值,則根據業務需求,傳回空或者抛異常。

如果使用者一直通路一個資料庫不存在的資料,比如id為-1的資料,就會導緻每次請求都會先去緩存查一次,然後再去資料庫查一次,造成嚴重的性能問題。這種情況就叫緩存穿透。

解決方案

以下幾種解決方案:

對請求參數做校驗,比如使用者鑒權校驗,id做基礎校驗,id <= 0的直接攔截。

如果查詢到資料庫沒有值,也将對應的key存進緩存中,value為null。這樣下次查詢就直接從緩存傳回了。但這裡的key的緩存時間應該比較短,比如30s。防止後面在資料庫插入了這條資料,而使用者擷取不到。

使用布隆過濾器,判斷一個key是否已經查過了,如果已經查過了,就不去資料庫查詢。

4種常見的緩存問題及解決方案詳解前言緩存穿透緩存擊穿緩存雪崩雙寫不一緻最後

緩存擊穿

緩存擊穿指的是,一個key的通路量非常大,比如某秒殺活動,有1w/s的并發量。這個key在某一時刻過期,那這些大量的請求就會一瞬間到資料庫,資料庫可能會直接崩潰。

緩存擊穿的解決方案也有幾種,可以配合使用:

對于熱點資料,慎重考慮過期時間,確定熱點期間key不會過期,甚至有些可以設定永不過期。

使用互斥鎖(比如Java的多線程鎖機制),第一個線程通路key的時候就鎖住,等查詢資料庫傳回後,把值插入到緩存後再釋放鎖,這樣後面的請求就可以直接取緩存裡面的資料了。

緩存雪崩

緩存雪崩指的是,在某一時刻,多個key失效。這樣就會有大量的請求從緩存中擷取不到值,全部到資料庫。還有另一種情況,就是緩存伺服器當機,也算做緩存雪崩。

針對上述兩種情況,緩存雪崩有兩種解決方案:

對每個key的過期時間設定一個随機值,而不是所有key都相同。

使用高可用的分布式緩存叢集,確定緩存的高可用性,比如redis-cluster。

4種常見的緩存問題及解決方案詳解前言緩存穿透緩存擊穿緩存雪崩雙寫不一緻最後

雙寫不一緻

在使用資料庫緩存的時候,讀和寫的流程往往是這樣的:

讀取的時候,先讀取緩存,如果緩存中沒有,就直接從資料庫中讀取,然後取出資料後放入緩存

更新的時候,先删除緩存,再更新資料庫

所謂雙寫不一緻,就是在發生寫操作(更新)的時候或寫操作之後,可能會存在資料庫裡面的值和緩存中的值不同的情況。

為什麼更新的時候要先删除緩存,再更新資料庫?因為如果先更新資料庫,然後在删除緩存的時候失敗了,就會造成緩存裡面的值和資料庫的值不一緻。

然而這樣并不能完全避免雙寫不一緻問題。假設在大并發情景下,一個線程先删除緩存,然後取更新資料庫,這個時候另一個線程去取緩存,發現沒有值,于是去讀資料庫,然後把資料庫舊的值設定進緩存。等第一個線程更新完資料庫後,資料庫裡面就是新的值,而緩存裡面是舊的值,是以就存在了資料不一緻的問題。

一個比較簡單的解決辦法是把過期時間設定得比較低,這樣就隻有在緩存沒過期之前存在資料不一緻問題,在一些業務場景下也還能接受。

另一種解決方案是使用隊列輔助。先更新資料庫,再删除緩存。如果删除失敗,就放進隊列。然後另一個任務從隊列中取出消息,不斷去重試删除相應的key。

還有一種解決方案是使用對一個資料使用一個隊列,使讀寫操作串行化。比如對id為n的資料建立一個隊列。對這條資料的寫操作,删除緩存後,放進一個隊列;然後另一個線程過來了,發現沒有緩存,則把這個讀操作也放進這個隊列裡面。

歡迎大家關注我的公種浩【程式員追風】,文章都會在裡面更新,整理的資料也會放在裡面。

不過這樣會增加程式的複雜性,串行化也會降低程式的吞吐量,可能得不償失。一般主流的解決方案還是先删除緩存,再更新資料庫。可以滿足絕大部分需求。

最後

歡迎大家一起交流,喜歡文章記得點個贊喲,感謝支援!