緩存穿透

很多項目在使用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在同一時間內都失效了則會引起緩存雪崩現象,導致後端資料庫壓力聚增。

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


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)提前演練。在項目上線前,演練緩存層宕掉後,應用以及後端的負載情況以及可能出現的問題,在此基礎上做一些預案設定。

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


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

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

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

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

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

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

分享讓我們成長!


緩存雪崩

緩存雪崩是指緩存中的數據集體過期了或者是redis崩潰了,導致直接查詢資料庫,如果資料庫承受不起請求的壓力可能會導致系統崩潰。

緩存雪崩解決方法

1.使用 Redis Cluster 實現集群部署防止個別機器宕機而導致整個redis緩存不能用。

2.本地是用Ehcache緩存,主要業務進行服務降級限流,防止資料庫掛掉。

緩存穿透

緩存穿透是指大量請求資料庫中為空緩存中不存在的key值,導致大量的請求落到資料庫中請求,同樣會導致數據崩潰。

緩存穿透解決辦法

如果資料庫中查詢出來的數據為空,就設置一個默認值到redis中,這樣下次請求就不會再請求到資料庫中,同時也要給這個key值設置過期時間,防止資料庫有值了卻讀不到。

緩存並發

並發問題有很多,舉例個比較典型的問題「雙寫一致性問題」,就是說緩存和資料庫的緩存的數據不一致。

問題描述:

業務邏輯一:先更新資料庫然後刪除某個key的緩存,下一次請求過來,查詢到緩存為空,就會查詢資料庫,然後再更新這個key的值到緩存中。

這裡存在的問題是,更新完資料庫的值後,更新redis的時候更新失敗,這個時候就會存在資料庫和緩存的值不一致了。

業務邏輯二:先刪除緩存再更新資料庫。

這種邏輯也會存在問題,就是再刪除緩存後,還沒來得及更新資料庫的值,有個新請求過來,就會發現緩存為空,查詢資料庫的舊值出來更新到緩存中,這個時候,緩存和資料庫的值也是不一致的。

解決方法

1.將所有的請求發到一個jvm隊列中,按照順序取出來執行,這樣就可以保證緩存和資料庫的一致性了,但是這樣存在的問題是,性能下降,響應變慢。這是網路中流傳的解決方法,個人感覺不靠譜,會有很多問題,例如對於分散式服務怎麼處理?

2.業務邏輯一的邏輯再加個重試機制就可以了,將刪除失敗的情況進行重試就可以了。將刪除失敗的key放到消息隊列中重複消費刪除。

3.還有中解決方案是,使用主備同步的邏輯進行非同步更新緩存。如可以使用mysql的binlog記錄對redis進行刪除,這個就像mysql的主從複製操作了,失敗也用2中的失敗策略。


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


推薦閱讀:
相关文章