.

###redis的快取擊穿?

快取穿透是指查詢一個根本不存在的資料,快取層和儲存層都不會命中,但是出於容錯的考慮,如果從儲存層查不到資料則不寫入快取層,如圖 11-3 所示整個過程分為如下 3 步:

  1. 快取層不命中
  2. 儲存層不命中,所以不將空結果寫回快取
  3. 返回空結果

快取穿透將導致不存在的資料每次請求都要到儲存層去查詢,失去了快取保護後端儲存的意義。

快取穿透模型

快取穿透問題可能會使後端儲存負載加大,由於很多後端儲存不具備高併發性,甚至可能造成後端儲存宕掉。通常可以在程式中分別統計總呼叫數、快取層命中數、儲存層命中數,如果發現大量儲存層空命中,可能就是出現了快取穿透問題。

造成快取穿透的基本有兩個。第一,業務自身程式碼或者資料出現問題,第二,一些惡意攻擊、爬蟲等造成大量空命中,下面我們來看一下如何解決快取穿透問題。

快取穿透的解決方法

1)快取空物件

如下圖所示,當第 2 步儲存層不命中後,仍然將空物件保留到快取層中,之後再訪問這個資料將會從快取中獲取,保護了後端資料來源。

快取空值應對穿透問題

快取空物件會有兩個問題:

第一,空值做了快取,意味著快取層中存了更多的鍵,需要更多的記憶體空間 ( 如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類資料設定一個較短的過期時間,讓其自動剔除。

第二,快取層和儲存層的資料會有一段時間視窗的不一致,可能會對業務有一定影響。例如過期時間設定為 5 分鐘,如果此時儲存層添加了這個資料,那此段時間就會出現快取層和儲存層資料的不一致,此時可以利用訊息系統或者其他方式清除掉快取層中的空物件。

下面給出了快取空物件的實現虛擬碼:

2)布隆過濾器攔截

如下圖所示,在訪問快取層和儲存層之前,將存在的 key 用布隆過濾器提前儲存起來,做第一層攔截。例如: 一個個性化推薦系統有 4 億個使用者 ID,每個小時演算法工程師會根據每個使用者之前歷史行為做出來的個性化放到儲存層中,但是最新的使用者由於沒有歷史行為,就會發生快取穿透的行為,為此可以將所有有個性化推薦資料的使用者做成布隆過濾器。如果布隆過濾器認為該使用者 ID 不存在,那麼就不會訪問儲存層,在一定程度保護了儲存層。

開發提示:

有關布隆過濾器的相關知識,可以參考:https://en.wikipedia.org/wiki/Bloom_filter

可以利用 Redis 的 Bitmaps 實現布隆過濾器,GitHub 上已經開源了類似的方案,讀者可以進行參考:

https://github.com/erikdubbelboer/Redis-Lua-scaling-bloom-filter

使用布隆過濾器應對穿透問題

這種方法適用於資料命中不高,資料相對固定實時性低(通常是資料集較大)的應用場景,程式碼維護較為複雜,但是快取空間佔用少。

兩種方案對比

前面介紹了快取穿透問題的兩種解決方法 ( 實際上這個問題是一個開放問題,有很多解決方法 ),下面通過下表從適用場景和維護成本兩個方面對兩種方案進行分析。

快取空物件和布隆過濾器方案對比

###redis的快取雪崩?

從下圖可以很清晰出什麼是快取雪崩:由於快取層承載著大量請求,有效的保護了儲存層,但是如果快取層由於某些原因整體不能提供服務,於是所有的請求都會達到儲存層,儲存層的呼叫量會暴增,造成儲存層也會掛掉的情況。 快取雪崩的英文原意是 stampeding herd(奔逃的野牛),指的是快取層宕掉後,流量會像奔逃的野牛一樣,打向後端儲存。

快取層不可用引起的雪崩

預防和解決快取雪崩問題,可以從以下三個方面進行著手。

1)保證快取層服務高可用性。

和飛機都有多個引擎一樣,如果快取層設計成高可用的,即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,例如前面介紹過的 Redis Sentinel 和 Redis Cluster 都實現了高可用。

2)依賴隔離元件為後端限流並降級。

無論是快取層還是儲存層都會有出錯的概率,可以將它們視同為資源。作為併發量較大的系統,假如有一個資源不可用,可能會造成執行緒全部 hang 在這個資源上,造成整個系統不可用。降級在高併發系統中是非常正常的:比如推薦服務中,如果個性化推薦服務不可用,可以降級補充熱點資料,不至於造成前端頁面是開天窗。

在實際專案中,我們需要對重要的資源 ( 例如 Redis、 MySQL、 Hbase、外部介面 ) 都進行隔離,讓每種資源都單獨執行在自己的執行緒池中,即使個別資源出現了問題,對其他服務沒有影響。但是執行緒池如何管理,比如如何關閉資源池,開啟資源池,資源池閥值管理,這些做起來還是相當複雜的,這裡推薦一個 Java 依賴隔離工具 Hystrix(https://github.com/Netflix/Hystrix)