原文摘自:數據庫
緩存穿透、併發和失效,來自一線架構師的解決方案
https://community.qingcloud.com/topic/463後端
在咱們的實踐中,原文中有部分解決方案已通過時,在原文的基礎上,添加了幾個咱們經常使用的方案。緩存
咱們在用緩存的時候,不論是Redis或者Memcached,基本上會通用遇到如下三個問題:網絡
緩存穿透架構
緩存併發併發
緩存失效異步
同步、複製中斷高併發
注:上面三個圖會有什麼問題呢?網站
咱們在項目中使用緩存一般都是先檢查緩存中是否存在,若是存在直接返回緩存內容,若是不存在就直接查詢數據庫而後再緩存查詢結果返回。
這個時候若是咱們查詢的某一個數據在緩存中一直不存在,就會形成每一次請求都查詢DB
,這樣緩存就失去了意義,在流量大時,可能DB就掛掉了。
那這種問題有什麼好辦法解決呢?
要是有人利用不存在的key頻繁攻擊咱們的應用,這就是漏洞。有一個比較巧妙的做法是,能夠將這個不存在的key預先設定一個值。好比,"key" , 「&&」。
在返回這個&&值的時候,咱們的應用就能夠認爲這是不存在的key,那咱們的應用就能夠決定是否繼續等待繼續訪問,仍是放棄掉此次操做。若是繼續等待訪問,過一個時間輪詢點後,再次請求這個key,若是取到的值再也不是&&,則能夠認爲這時候key有值了,從而避免了透傳到數據庫,從而把大量的相似請求擋在了緩存之中。
你應該注意,這裏緩存未命中的緣由,更值得咱們關注。
當緩存空間滿了,同步失敗,網絡阻塞,緩存寫失敗等緣由,會出現緩存服務器上並無這個key。
或者由於同步中斷,在主從架構中,寫到主卻未同步到從的悲劇,就會出現請求穿透到DB層的狀況。
出現這樣的狀況,必定不能直接將請求穿透到DB層,避免DB當機影響其它業務。
咱們的解決方案能夠參考。
當業務中請求量特別高,緩存未命中的狀況,應該在創建DB保護的基礎上,放棄必定比例的請求,直接返回空
能夠隨機釋放一些請求到DB,控制好流量的話,能保證緩存重建且DB不受極端壓力
後端異步定時檢查緩存,主動創建這些緩存
部分頁面可緩存永不過時,後端異步定時檢查緩存,主動更新、重建緩存,後端失效也可實現業務降級
經過創建二級緩存,把以前成功獲取的緩存數據放到本機緩存,文件也好,共享內存也好,接受一些過時數據
有時候若是網站併發訪問高,一個緩存若是失效,可能出現多個進程同時查詢DB,同時設置緩存的狀況,若是併發確實很大,這也可能形成DB壓力過大,還有緩存頻繁更新的問題。
我如今的想法是對緩存查詢加鎖,若是KEY不存在,就加鎖,而後查DB入緩存,而後解鎖;其餘進程若是發現有鎖就等待,而後等解鎖後返回數據或者進入DB查詢。
這種狀況和剛纔說的預先設定值問題有些相似,只不過利用鎖的方式,會形成部分請求等待。
引發這個問題的主要緣由仍是高併發的時候,平時咱們設定一個緩存的過時時間時,可能有一些會設置1分鐘啊,5分鐘這些,併發很高時可能會出在某一個時間同時生成了不少的緩存,而且過時時間都同樣,這個時候就可能引起一當過時時間到後,這些緩存同時失效,請求所有轉發到DB,DB可能會壓力太重。
那如何解決這些問題呢?
其中的一個簡單方案就時講緩存失效時間分散開,好比咱們能夠在原有的失效時間基礎上增長一個隨機值,好比1-5分鐘隨機,這樣每個緩存的過時時間的重複率就會下降,就很難引起集體失效的事件。
咱們討論的第二個問題時針對同一個緩存,第三個問題時針對不少緩存。
接下來咱們將發表一些本身的緩存高可用實踐,如《基於雲平臺的緩存集羣高可用實踐》
,歡迎關注。
總結
一、緩存穿透:查詢一個必然不存在的數據。好比文章表,查詢一個不存在的id,每次都會訪問DB,若是有人惡意破壞,極可能直接對DB形成影響。
二、緩存失效:若是緩存集中在一段時間內失效,DB的壓力凸顯。這個沒有完美解決辦法,但能夠分析用戶行爲,儘可能讓失效時間點均勻分佈。
當發生大量的緩存穿透,例如對某個失效的緩存的大併發訪問就形成了緩存雪崩。
精彩問答
問題:如何解決DB和緩存一致性問題?
當修改了數據庫後,有沒有及時修改緩存。這種問題,之前有過實踐,修改數據庫成功,而修改緩存失敗的狀況,最主要就是緩存服務器掛了。而由於網絡問題引發的沒有及時更新,能夠經過重試機制來解決。而緩存服務器掛了,請求首先天然也就沒法到達,從而直接訪問到數據庫。那麼咱們在修改數據庫後,沒法修改緩存,這時候能夠將這條數據放到數據庫中,同時啓動一個異步任務定時去檢測緩存服務器是否鏈接成功,一旦鏈接成功則從數據庫中按順序取出修改數據,依次進行緩存最新值的修改。
問題:問下緩存穿透那塊!例如,一個用戶查詢文章,經過ID查詢,按照以前說的,是將緩存的KEY預先設置一個值,,若是經過ID插過來,發現是預先設定的一個值,好比說是「&&」,那以後的繼續等待訪問是什麼意思,這個ID何時會真正被附上用戶所須要的值呢?
我剛說的主要是我們經常使用的後面配置,前臺獲取的場景。前臺沒法獲取相應的key,則等待,或者放棄。當在後臺配置界面上配置了相關key和value以後,那麼之前的key &&也天然會被替換掉。你說的那種狀況,天然也應該會有一個進程會在某一個時刻,在緩存中設置這個ID,再有新的請求到達的時候,就會獲取到最新的ID和value。
問題:其實用Redis的話,那天看到一個不錯的例子,雙key,有一個當時生成的一個附屬key來標識數據修改到期時間,而後快到的時候去從新加載數據,若是以爲key多能夠把結束時間放到主key中,附屬key起到鎖的功能。
這種方案,以前咱們實踐過。這種方案會產生雙份數據,並且須要同時控制附屬key與key之間的關係,操做上有必定複雜度。
問題:多級緩存是什麼概念呢?
多級緩存就像我今天以前給你們發的文章裏面提到了,將Ehcache與Redis作二級緩存,就像我以前寫的文章 http://www.jianshu.com/p/2cd6ad416a5a 提到過的。但一樣會存在一致性問題,若是咱們須要強一致性的話,緩存與數據庫同步是會存在時間差的,因此咱們在具體開發的過程當中,必定要根據場景來具體分析,二級緩存更多的解決是,緩存穿透與程序的健壯性,當集中式緩存出現問題的時候,咱們的應用可以繼續運行。