1. 程式人生 > >快取世界中的三大問題及解決方案

快取世界中的三大問題及解決方案

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

1. 快取穿透

在大多數網際網路應用中,快取的使用方式如下圖所示:
title

  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,如下圖所示:

title

當業務系統有查詢請求的時候,首先去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向那些被拒絕的請求直接返回一個預設結果,被稱為“降級”

3. 熱點資料集中失效

3.1 什麼是熱點資料集中失效?

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

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

title

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

3.2 解決方案

3.2.1 互斥鎖

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

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

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

3.3.2 設定不同的失效時間

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