4種常見的緩存問題及解決方案詳解

前言程序員

使用緩存能夠緩解大流量壓力,顯著提升程序的性能。咱們在使用緩存系統時,尤爲是大併發狀況下,常常會遇到一些「疑難雜症」。本文總結了一些使用緩存時常見的問題及解決方案,之後在遇到這類問題時能夠做爲參考,在設計緩存系統的時候也應該考慮這些常見的狀況。redis

爲了表述方便,本文以數據庫查詢緩存爲例,使用緩存能夠減少對數據庫的壓力。數據庫

v2-3129965b5ad24d6178225b36370e2a86_hd.png

緩存穿透緩存

咱們在使用緩存時,每每先嚐試去緩存中取值,若是沒有,再去數據庫取值,若是數據庫也沒有值,則根據業務需求,返回空或者拋異常。服務器

若是用戶一直訪問一個數據庫不存在的數據,好比id爲-1的數據,就會致使每次請求都會先去緩存查一次,而後再去數據庫查一次,形成嚴重的性能問題。這種狀況就叫緩存穿透。多線程

解決方案併發

如下幾種解決方案:app

  • 對請求參數作校驗,好比用戶鑑權校驗,id作基礎校驗,id <= 0的直接攔截。分佈式

  • 若是查詢到數據庫沒有值,也將對應的key存進緩存中,value爲null。這樣下次查詢就直接從緩存返回了。但這裏的key的緩存時間應該比較短,好比30s。防止後面在數據庫插入了這條數據,而用戶獲取不到。ide

  • 使用布隆過濾器,判斷一個key是否已經查過了,若是已經查過了,就不去數據庫查詢。

v2-42b6414d4caac70b6ee952dbeb3fe918_hd.png

緩存擊穿

緩存擊穿指的是,一個key的訪問量很是大,好比某秒殺活動,有1w/s的併發量。這個key在某一時刻過時,那這些大量的請求就會一瞬間到數據庫,數據庫可能會直接崩潰。

解決方案

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

  • 對於熱點數據,慎重考慮過時時間,確保熱點期間key不會過時,甚至有些能夠設置永不過時。

  • 使用互斥鎖(好比Java的多線程鎖機制),第一個線程訪問key的時候就鎖住,等查詢數據庫返回後,把值插入到緩存後再釋放鎖,這樣後面的請求就能夠直接取緩存裏面的數據了。

緩存雪崩

緩存雪崩指的是,在某一時刻,多個key失效。這樣就會有大量的請求從緩存中獲取不到值,所有到數據庫。還有另外一種狀況,就是緩存服務器宕機,也算作緩存雪崩。

解決方案

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

  • 對每一個key的過時時間設置一個隨機值,而不是全部key都相同。

  • 使用高可用的分佈式緩存集羣,確保緩存的高可用性,好比redis-cluster。

v2-f17dc8c9886fff571bc3f9fc60404556_hd.png

雙寫不一致

在使用數據庫緩存的時候,讀和寫的流程每每是這樣的:

  • 讀取的時候,先讀取緩存,若是緩存中沒有,就直接從數據庫中讀取,而後取出數據後放入緩存

  • 更新的時候,先刪除緩存,再更新數據庫

所謂雙寫不一致,就是在發生寫操做(更新)的時候或寫操做以後,可能會存在數據庫裏面的值和緩存中的值不一樣的狀況。

爲何更新的時候要先刪除緩存,再更新數據庫?由於若是先更新數據庫,而後在刪除緩存的時候失敗了,就會形成緩存裏面的值和數據庫的值不一致。

然而這樣並不能徹底避免雙寫不一致問題。假設在大併發情景下,一個線程先刪除緩存,而後取更新數據庫,這個時候另外一個線程去取緩存,發現沒有值,因而去讀數據庫,而後把數據庫舊的值設置進緩存。等第一個線程更新完數據庫後,數據庫裏面就是新的值,而緩存裏面是舊的值,因此就存在了數據不一致的問題。

一個比較簡單的解決辦法是把過時時間設置得比較低,這樣就只有在緩存沒過時以前存在數據不一致問題,在一些業務場景下也還能接受。

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

還有一種解決方案是使用對一個數據使用一個隊列,使讀寫操做串行化。好比對id爲n的數據創建一個隊列。對這條數據的寫操做,刪除緩存後,放進一個隊列;而後另外一個線程過來了,發現沒有緩存,則把這個讀操做也放進這個隊列裏面。

歡迎你們關注個人公種浩【程序員追風】,文章都會在裏面更新,整理的資料也會放在裏面。

不過這樣會增長程序的複雜性,串行化也會下降程序的吞吐量,可能得不償失。通常主流的解決方案仍是先刪除緩存,再更新數據庫。能夠知足絕大部分需求。

最後

歡迎你們一塊兒交流,喜歡文章記得點個贊喲,感謝支持!

相關文章
相關標籤/搜索