03.06 如何解決Redis緩存雪崩、緩存穿透、緩存併發等問題?

-二小姐-


緩存穿透

很多項目在使用Redis或其他緩存框架的時候,都是先查詢緩存,查詢不的話再查詢數據庫,查到之後再放到內存中;如果一個key值本身就不存在,那麼每一次都會查詢數據庫,也就是常說的【緩存穿透】。

應對方法:

  • 如果在Redis中查詢不到,並且查詢數據庫也沒有結果,那麼就將這個key寫入到Redis中,value=空,並設置一個超時過期時間,例如五分鐘,那麼五分鐘以內的對這個可以的所有查詢就可以攔截下來,如果數據庫有key對應的數據了,那麼五分鐘後Redis中的緩存過期,會訪問數據庫並加載緩存;但是如果被惡意攻擊,每次請求的key都不相同且不存在,那麼依然會穿透到數據庫;

  • 布隆過濾器:將可能存在的數據Hash到一個足夠大的bitmap上,它可以告訴你 “某個key一定不存在或者可能存在”,一個一定不存在的數據會被bitmap攔截。

緩存雪崩

很多時候,Redis中的緩存是要設置過期時間的,假如Redis中的數據,過期時間都設置成一樣的,那麼到了時間之後,全部緩存過期失效,下一秒所有的請求都會訪問數據庫,那麼數據庫可能因為訪問量多大導致“崩潰”,這就是緩存雪崩。

應對方法:

  • 最暴力的解決辦法,緩存不設置自動過期時間,只要緩存不崩,數據庫就不會崩。

  • 另外一個辦法,就是讓緩存過期時間不那麼一致,比如一批緩存數據24小時後過期,那麼就在這個基礎上,每條緩存的過期時間前後隨機1-6000秒(1-10分鐘)。

緩存併發

大多數時候,我們的程序訪問Redis都不可能是單線程,那麼當多個Client併發對Redis進行set key操作的時候,可能會產生一些問題;其實Redis本身是單線程的,這種時候會按照先後順序進行操作;或者把操作放在隊列中,按順序執行;

但比如這種情況:

  1. token過期,有兩個線程都去重新獲取token;

  2. 線程1獲取token1;

  3. 線程2獲取到token2,此時token1過期;

  4. 線程1把token1放到Redis,再拿著token1去調用服務,發現過期了,繼續去請求token3,此時token2過期;

  5. 線程2把token2放到Redis,再拿著token2去調用服務,發現過期了,繼續去請求token4,此時token3過期;

  6. ... ...

這就需要我們在更新緩存的時候,做一些控制了。

我將持續分享Java開發、架構設計、程序員職業發展等方面的見解,希望能得到你的關注。


會點代碼的大叔


Redis因其簡單、高效的特點被廣泛應用,如今中小型網站都在使用Redis了。我們在使用Redis時,在高併發場景下,Redis容易出現緩存併發、緩存穿透及雪崩的現象。

緩存併發、緩存雪崩、緩存穿透這三者還是有區別的,我們先來了解一下。

1、緩存併發

我們說的緩存併發指的是多個Redis客戶端同時SET Key時會引起併發問題。我們知道,Redis是單線程的,在多個Client併發操作時,秉承“先發起先執行”的原則,其它的處於阻塞狀態。

常見緩存併發有兩種場景:

  • 緩存過期後會從後端數據庫查詢數據然後存入Redis緩存,但在高併發情況下可能在還沒來得及將庫中查出來的數據存入Redis時,其它Client又從數據庫裡查詢數據再存入Redis了。這樣一來會造成多個請求併發的從數據庫獲取數據,對後端數據庫會造成壓力。

  • 在高併發場景下,某個Key正在更新時,可能有很多Client在獲取此Key的數據,這樣會導致“髒數據”。

如何解決緩存併發問題呢?我們常藉助“鎖”來實現,具體實現邏輯為:

在更新緩存或者讀取過期緩存的情況下,我們先獲取“鎖”,當完成了緩存數據的更新後再釋放鎖,這樣一來,其它的請求需要在釋放鎖之後執行,會犧牲一點時間。

2、緩存穿透

什麼是緩存穿透現象呢?我們先來說下緩存正常的邏輯。

正常情況下我們都是根據緩存Key查看是否存在值或已過期,如果不存在我們則向後端數據庫查詢並將查詢結果存入緩存中(數據庫有結果時存入Redis,沒結果時就沒有存入Redis),下次請求時只要緩存沒過期就直接從緩存中獲取數據。

但是,如果我們在上一步過程中,從數據庫查詢出的結果是空的,所以沒有存入Redis,然後又跑到後端數據庫進行查詢,即每一次請求都會落到數據庫去查詢,這樣就導致了緩存命中率低的現象,這就是緩存穿透。

所以說緩存穿透可以理解為訪問了一個不存在的Key,緩存無效,沒有發揮作用,每次查詢流量最終落在了數據庫上面。

還有一種情況,即使是一個存在的Key,在它緩存過期的那一瞬間,如果網站併發大,那在此Key沒有重新生效時,所有的請求全部擊穿到數據庫上了。

如果避免緩存穿透呢?最簡單的方式就是即使數據庫查詢結果是空的,我們給一個缺省值或者直接將空值存入Redis。

3、緩存雪崩

什麼是緩存雪崩呢?緩存雪崩指的是在一段時間段內,Redis絕大部分Key集體失效的現象。

造成這種現象的原因很簡單:大量的Key設置的過期時間是相同的,或者都在同一段時間段內。當緩存在同一段時間段內集體失效後,你會發現數據庫壓力一下子就上去了(DB洪峰),最終引起雪崩現象。

如何避免呢?給兩個方案參考:

  • 給所有緩存時間設置一個固定的TTL+隨機時間TTL,這樣能保證所有Key不會同一時間內集體過期;

  • 緩存副本機制,給每個Key設置一個副本,副本的TTL時間較長。


綜上,在緩存併發情況嚴重時,熱點Key在失效的瞬間會引起緩存擊穿(小雪崩),如果所有的Key在同一時間內都失效了則會引起緩存雪崩現象,導致後端數據庫壓力聚增。

以上就是我的觀點,對於這個問題大家是怎麼看待的呢?歡迎在下方評論區交流 ~ 我是科技領域創作者,歡迎關注我瞭解更多科技知識!


網絡圈


凌晨四點醒來看到這個問題,很有感觸,索性覺也不睡了和大家好好聊聊這個話題,因為自己工作中遇到過緩存雪崩問題。緩存的概念以及使用我就不多說了,直接來聊乾貨!關注必回!


  • 緩存穿透和擊穿,簡單來說就是一個緩存數據被請求時沒有直接從緩存中獲取到值,轉而繞過緩存,直接讀取底層數據,中間層緩存的舒壓作用沒有得到體現,訪問高峰期大批量的請求繞過緩存直接打到數據庫上的情況。換一種更容易理解的表達方式,大部分緩存系統,都是按照key值去緩存查詢,如果不存在對應的value,或者緩存讀取超時,就會去DB中查找 。如果請求的併發量很大,就會對後端的DB系統造成很大的壓力。這就叫做緩存穿透和擊穿。事故緩存穿透和擊穿的誘發原因大概率有以下幾種。1.大量緩存同時過期,2.單臺緩存服務器短時間內因為網絡問題超時3.線程池鏈接被用盡!4.惡意穿透,惡意請求大量訪問緩存中不存在的數據!

擊穿和穿透場景不盡相同,擊穿熱點key在高峰期失效!穿透大概率指惡意訪問緩存中不存在的key,故而解決方式也有差別,手機碼字不易我就糅合在一起說。方案一,互斥鎖排隊處理,key獲取value值為空時,鎖上,從數據庫中load數據後再釋放鎖。若其它線程獲取鎖失敗,則等待一段時間後重試。方案二,網關中進行接口限流與熔斷、降級。方案三,通過谷歌的布魯過濾器進行快速篩選過濾。

  • 緩存雪崩,最可怕的就是緩存雪崩,雪崩會導致一系列連鎖反應。緩存由於某些原因(比如 宕機、cache服務掛了或者不響應)整體crash掉了,導致大量請求到達後端數據庫,從而導致數據庫崩潰,整個系統崩潰,發生災難。當然緩存穿透和擊穿也有可能引起雪崩。

    解決方案有以下幾種,方案一,多機房部署,服務高可用,即使某一機房因為外界因素掛掉也還有備用。方案二,熔斷,根據一定規則判斷緩存失效之後進行熔斷降級處理。方案三,數據及時備份,發生事故之後快速恢復。

  • 緩存併發,這裡的併發指的是多個redis的client同時set key引起的併發問題。比較有效的解決方案就是把redis.set操作放在隊列中使其串行化。

關於緩存這塊兒,還牽扯到數據一致性,緩存同步,redis集群等諸多重要知識點,手機碼字,實屬不易,以後再更新吧!大家對於緩存有獨特見解的歡迎留言討論!

分享讓我們成長!


狂客說技術


緩存穿透、擊穿與雪崩是常見的由於併發量大而導致的緩存問題。

#緩存穿透

緩存穿透指的是使用不存在的Key請求,導致緩存無法命中,每次請求都穿透到數據庫,使數據庫壓力過大,甚至宕機。

通常解決方案是將空值緩存起來,再次接到同樣的請求時,就可以命中緩存空值,直接返回而不再請求數據庫。當然,如果每次的請求Key都不一樣,導致空值緩存方案不起作用,這正常依賴於參數的合法性校驗與數據摘要的校驗,盡最大可能使用戶無法修改請求參數。

更進一步的做法是針對高併發接口的數據使用布隆過濾器來判斷請求的數據是否存在,如果不存在直接返回,避免數據庫的查詢操作。

什麼是布隆過濾器

本質上布隆過濾器是一種數據結構,比較巧妙的概率型數據結構(probabilistic data structure),特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”。

(祥細原理在後續文章中介紹)

#緩存擊穿

緩存擊穿是指在高併發場景下,當某個熱點Key過期時,大量的併發請求同時打到數據庫,造成數據庫壓力巨大甚至打死的情況。

緩存擊穿的處理方案有:

  • 若緩存的數據是基本不會發生更新的,則可嘗試將該熱點數據設置為永不過期。
  • 若緩存的數據更新不頻繁,且緩存刷新的整個流程耗時較少的情況下,則可以採用基於 redis、zookeeper 等分佈式中間件的分佈式互斥鎖,或者本地互斥鎖以保證僅少量的請求能請求數據庫並重新構建緩存,其餘線程則在鎖釋放後能訪問到新緩存。
  • 若緩存的數據更新頻繁或者緩存刷新的流程耗時較長的情況下,可以利用定時線程在緩存過期前主動的重新構建緩存或者延後緩存的過期時間,以保證所有的請求能一直訪問到對應的緩存。


#緩存雪崩

    緩存雪崩是指緩存重啟或者大量的緩存數據集中在某一時間內失效,導致所有請求完全打到數據庫,造成數據庫負載巨大、壓垮。

    解決方案就是避免大量緩存的Key集中式過期,如果需要集中,也應該設置偏差值。實現相對集中(如統一過期時間 + 隨機秒數),而不是統一集中。


java架構筆記


Redis緩存雪崩解決方案

由於緩存層承載著大量請求,有效地保護了存儲層,但是如果緩存層由於某些原因不能提供服務,於是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會級聯宕機的情況。緩存雪崩的英文原意是stampeding herd(奔逃的野牛),指的是緩存層宕掉後,流量會像奔逃的野牛一樣,打向後端存儲。

預防和解決緩存雪崩問題,可以從以下三個方面進行著手。
1)保證緩存層服務高可用性。和飛機都有多個引擎一樣,如果緩存層設計成高可用的,即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,例如前面介紹過的Redis Sentinel和Redis Cluster都實現了高可用。
2)依賴隔離組件為後端限流並降級。無論是緩存層還是存儲層都會有出錯的概率,可以將它們視同為資源。作為併發量較大的系統,假如有一個資源不可用,可能會造成線程全部阻塞(hang)在這個資源上,造成整個系統不可用。降級機制在高併發系統中是非常普遍的:比如推薦服務中,如果個性化推薦服務不可用,可以降級補充熱點數據,不至於造成前端頁面是開天窗。在實際項目中,我們需要對重要的資源(例如Redis、MySQL、
HBase、外部接口)都進行隔離,讓每種資源都單獨運行在自己的線程池 中,即使個別資源出現了問題,對其他服務沒有影響。但是線程池如何管理,比如如何關閉資源池、開啟資源池、資源池閥值管理,這些做起來還是相當複雜的。這裡推薦一個Java依賴隔離工具Hystrix(https://github.com/netflix/hystrix),如圖11-15所示。Hystrix是解決依賴隔離的利器,但是該內容已經超出本書的範圍,同時只適用於Java應用, 所以這裡不會詳細介紹。
3)提前演練。在項目上線前,演練緩存層宕掉後,應用以及後端的負載情況以及可能出現的問題,在此基礎上做一些預案設定。

雲計算那些事兒


緩存 ,一定要具有個性化數據篩選的設置。這三個問題就能夠解決了。換言之,緩存,是不具有通用性的。這是解決緩存諸多問題的關鍵。


分享到:


相關文章: