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

目前的IO設備遠不能滿足互聯網應用海量的讀寫請求。於是便出現了緩存,利用內存的高速讀寫性能來應付海量的查詢請求。然而內存資源非常寶貴,將全量數據存儲在內存中顯然是不切合實際的。因此目前採用內存和IO結合的方式,內存只存儲熱點數據,而IO設備存儲全量數據。
緩存的設計包含很多技巧,設計不當將會導致嚴重的後果。本文將介紹緩存使用中常見的三大問題,並給出相應的解決方案。

1. 緩存穿透

在大多數互聯網應用中,緩存的使用方式如下圖所示:
在這裏插入圖片描述
當業務系統發起某一個查詢請求時,首先判斷緩存中是否有該數據;
如果緩存中存在,則直接返回數據;
如果緩存中不存在,則再查詢數據庫,然後返回數據。

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

1.1 什麼是緩存穿透?

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

1.2 緩存穿透的危害

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

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

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

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

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的介紹請參閱:https://segmentfault.com/a/1190000005988895

3. 熱點數據集中失效

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

我們一般都會給緩存設定一個失效時間,過了失效時間後,該數據庫會被緩存直接刪除,從而一定程度上保證數據的實時性。
但是,對於一些請求量極高的熱點數據而言,一旦過了有效時間,此刻將會有大量請求落在數據庫上,從而可能會導致數據庫崩潰。其過程如下圖所示:
在這裏插入圖片描述

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

3.2 解決方案

3.2.1 互斥鎖

我們可以使用緩存自帶的鎖機制,當第一個數據庫查詢請求發起後,就將緩存中該數據上鎖;此時到達緩存的其他查詢請求將無法查詢該字段,從而被阻塞等待;當第一個請求完成數據庫查詢,並將數據更新值緩存後,釋放鎖;此時其他被阻塞的查詢請求將可以直接從緩存中查到該數據。
當某一個熱點數據失效後,只有第一個數據庫查詢請求發往數據庫,其餘所有的查詢請求均被阻塞,從而保護了數據庫。但是,由於採用了互斥鎖,其他請求將會阻塞等待,此時系統的吞吐量將會下降。這需要結合實際的業務考慮是否允許這麼做。
互斥鎖可以避免某一個熱點數據失效導致數據庫崩潰的問題,而在實際業務中,往往會存在一批熱點數據同時失效的場景。那麼,對於這種場景該如何防止數據庫過載呢?

3.3.2 設置不同的失效時間

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章