緩存世界中的三大問題及解決方案

目前的IO設備遠不能滿足互聯網應用海量的讀寫請求。於是便出現了緩存,利用內存的高速讀寫性能來應付海量的查詢請求。然而內存資源非常寶貴,將全量數據存儲在內存中顯然是不切合實際的。因此目前採用內存和IO結合的方式,內存只存儲熱點數據,而IO設備存儲全量數據。

緩存的設計包含很多技巧,設計不當將會導致嚴重的後果。本文將介紹緩存使用中常見的三大問題,並給出相應的解決方案。

1. 緩存穿透

在大多數互聯網應用中,緩存的使用方式如下圖所示:

  1. 當業務系統發起某一個查詢請求時,首先判斷緩存中是否有該數據;
  2. 如果緩存中存在,則直接返回數據;
  3. 如果緩存中不存在,則再查詢資料庫,然後返回數據。

了解了上述過程後,下面說說緩存穿透。

1.1 什麼是緩存穿透?

業務系統要查詢的數據根本就存在!當業務系統發起查詢時,按照上述流程,首先會前往緩存中查詢,由於緩存中不存在,然後再前往資料庫中查詢。由於該數據壓根就不存在,因此資料庫也返回空。這就是緩存穿透。

綜上所述:業務系統訪問壓根就不存在的數據,就稱為緩存穿透。

1.2 緩存穿透的危害

如果存在海量請求查詢壓根就不存在的數據,那麼這些海量請求都會落到資料庫中,資料庫壓力劇增,可能會導致系統崩潰(你要知道,目前業務系統中最脆弱的就是IO,稍微來點壓力它就會崩潰,所以我們要想種種辦法保護它)。

1.3 為什麼會發生緩存穿透?

發生緩存穿透的原因有很多,一般為如下兩種:

  1. 惡意攻擊,故意營造大量不存在的數據請求我們的服務,由於緩存中並不存在這些數據,因此海量請求均落在資料庫中,從而可能會導致資料庫崩潰。
  2. 代碼邏輯錯誤。這是程序員的鍋,沒啥好講的,開發中一定要避免!

1.4 緩存穿透的解決方案

下面來介紹兩種防止緩存穿透的手段。

1.4.1 緩存空數據

之所以發生緩存穿透,是因為緩存中沒有存儲這些空數據的key,導致這些請求全都打到資料庫上。

那麼,我們可以稍微修改一下業務系統的代碼,將資料庫查詢結果為空的key也存儲在緩存中。當後續又出現該key的查詢請求時,緩存直接返回null,而無需查詢資料庫。

1.4.2 BloomFilter

第二種避免緩存穿透的方式即為使用BloomFilter。

它需要在緩存之前再加一道屏障,裡面存儲目前資料庫中存在的所有key,如下圖所示:

當業務系統有查詢請求的時候,首先去BloomFilter中查詢該key是否存在。若不存在,則說明資料庫中也不存在該數據,因此緩存都不要查了,直接返回null。若存在,則繼續執行後續的流程,先前往緩存中查詢,緩存中沒有的話再前往資料庫中的查詢。

1.4.3 兩種方案的比較

這兩種方案都能解決緩存穿透的問題,但使用場景卻各不相同。

對於一些惡意攻擊,查詢的key往往各不相同,而且數據賊多。此時,第一種方案就顯得提襟見肘了。因為它需要存儲所有空數據的key,而這些惡意攻擊的key往往各不相同,而且同一個key往往只請求一次。因此即使緩存了這些空數據的key,由於不再使用第二次,因此也起不了保護資料庫的作用。

因此,對於空數據的key各不相同key重複請求概率低的場景而言,應該選擇第二種方案。而對於空數據的key數量有限key重複請求概率較高的場景而言,應該選擇第一種方案。

2. 緩存雪崩

2.1 什麼是緩存雪崩?

通過上文可知,緩存其實扮演了一個保護資料庫的角色。它幫資料庫抵擋大量的查詢請求,從而避免脆弱的資料庫受到傷害。

如果緩存因某種原因發生了宕機,那麼原本被緩存抵擋的海量查詢請求就會像瘋狗一樣湧向資料庫。此時資料庫如果抵擋不了這巨大的壓力,它就會崩潰。

這就是緩存雪崩。

2.2 如何避免緩存雪崩?

2.2.1 使用緩存集群,保證緩存高可用

也就是在雪崩發生之前,做好預防手段,防止雪崩的發生。

PS:關於分散式高可用問題不是今天討論的重點,套路就那些,後面會有高可用的相關文章,盡請關注。

2.2.2 使用Hystrix

Hystrix是一款開源的「防雪崩工具」,它通過 熔斷、降級、限流三個手段來降低雪崩發生後的損失。

Hystrix就是一個Java類庫,它採用命令模式,每一項服務處理請求都有各自的處理器。所有的請求都要經過各自的處理器。處理器會記錄當前服務的請求失敗率。一旦發現當前服務的請求失敗率達到預設的值,Hystrix將會拒絕隨後該服務的所有請求,直接返回一個預設的結果。這就是所謂的「熔斷」。當經過一段時間後,Hystrix會放行該服務的一部分請求,再次統計它的請求失敗率。如果此時請求失敗率符合預設值,則完全打開限流開關;如果請求失敗率仍然很高,那麼繼續拒絕該服務的所有請求。這就是所謂的「限流」。而Hystrix向那些被拒絕的請求直接返回一個預設結果,被稱為「降級」

更多Hystrix的介紹請參閱:segmentfault.com/a/1190

3. 熱點數據集中失效

3.1 什麼是熱點數據集中失效?

我們一般都會給緩存設定一個失效時間,過了失效時間後,該資料庫會被緩存直接刪除,從而一定程度上保證數據的實時性。

但是,對於一些請求量極高的熱點數據而言,一旦過了有效時間,此刻將會有大量請求落在資料庫上,從而可能會導致資料庫崩潰。其過程如下圖所示:

如果某一個熱點數據失效,那麼當再次有該數據的查詢請求[req-1]時就會前往資料庫查詢。但是,從請求發往資料庫,到該數據更新到緩存中的這段時間中,由於緩存中仍然沒有該數據,因此這段時間內到達的查詢請求都會落到資料庫上,這將會對資料庫造成巨大的壓力。此外,當這些請求查詢完成後,都會重複更新緩存。

3.2 解決方案

3.2.1 互斥鎖

我們可以使用緩存自帶的鎖機制,當第一個資料庫查詢請求發起後,就將緩存中該數據上鎖;此時到達緩存的其他查詢請求將無法查詢該欄位,從而被阻塞等待;當第一個請求完成資料庫查詢,並將數據更新值緩存後,釋放鎖;此時其他被阻塞的查詢請求將可以直接從緩存中查到該數據。

當某一個熱點數據失效後,只有第一個資料庫查詢請求發往資料庫,其餘所有的查詢請求均被阻塞,從而保護了資料庫。但是,由於採用了互斥鎖,其他請求將會阻塞等待,此時系統的吞吐量將會下降。這需要結合實際的業務考慮是否允許這麼做。

互斥鎖可以避免某一個熱點數據失效導致資料庫崩潰的問題,而在實際業務中,往往會存在一批熱點數據同時失效的場景。那麼,對於這種場景該如何防止資料庫過載呢?

3.3.2 設置不同的失效時間

當我們向緩存中存儲這些數據的時候,可以將他們的緩存失效時間錯開。這樣能夠避免同時失效。如:在一個基礎時間上加/減一個隨機數,從而將這些緩存的失效時間錯開。


推薦閱讀:

什麼是架構設計
Spring中的事務管理為我們做了哪些事?
架構師手記17 如何設計一個實時大數據用戶行為分析系統 (一)
厲害了,螞蟻金服!創造了中國自己的資料庫OceanBase(上)
阿里雲攜領先SDN能力,亮相全球網路技術盛會ONS

TAG:緩存 | 架構 | 架構師 |