前言 昨天有讀者朋友留言,想要陳某寫一篇防止緩存穿透的文章,今天特意寫了一篇。 文章目錄如下: 什麼是緩存穿透? 緩存穿透其實是指從緩存中沒有查到數據,而不得不從後端系統(比如資料庫)中查詢的情況。 緩存畢竟是在記憶體中,不可能所有的數據都存儲在 Redis 中,因此少量的緩存穿透是不可避免的,也是系 ...
前言
- 昨天有讀者朋友留言,想要陳某寫一篇防止緩存穿透的文章,今天特意寫了一篇。
- 文章目錄如下:
什麼是緩存穿透?
- 緩存穿透其實是指從緩存中沒有查到數據,而不得不從後端系統(比如資料庫)中查詢的情況。
- 緩存畢竟是在記憶體中,不可能所有的數據都存儲在 Redis 中,因此少量的緩存穿透是不可避免的,也是系統能夠承受的,但是一旦在瞬間發生大量的緩存穿透,資料庫的壓力會瞬間增大,後果可想而知。
- 在開發中使用緩存的方案如下圖,在查詢資料庫之前會先查詢 Redis:
- 緩存穿透的整個過程分為如下幾個步驟:
- 應用查詢緩存,緩存不命中
- DB 層查詢不命中,不將空結果緩存
- 返回空結果
- 下一個請求繼續重覆1,2,3步。
解決方案
- 萬事萬物都是相生相剋,既然出現了緩存穿透,就一定有避免的方案。
- 下麵介紹兩種緩存的方案,分別是
緩存空值
、布隆過濾器
。
緩存空值
- 回顧緩存穿透的定義知道,大量空值沒有緩存導致重覆的訪問 DB 層,由此解決方案也是很明顯了,直接將返回的空值也緩存即可。此時的執行步驟如下圖:
- 如上圖所示,如果緩存不命中,查詢 DB 層之後,直接將空值緩存在 Redis 中。偽代碼如下:
Object nullValue = new Object();
try {
Object valueFromDB = getFromDB(uid); //從資料庫中查詢數據
if (valueFromDB == null) {
cache.set(uid, nullValue, 10); //如果從資料庫中查詢到空值,就把空值寫入緩存,設置較短的超時時間
} else {
cache.set(uid, valueFromDB, 1000);
}
} catch(Exception e) {
// 出現異常也要寫入緩存
cache.set(uid, nullValue, 10);
}
- 通過偽代碼可以很清楚的瞭解了緩存空值的流程,但是需要註意以下問題:
- 緩存一定要設置過期時間:因為空值並不是準確的業務數據,並且會占用緩存空間,所以要給空值加上一個過期時間,使得能夠在短期之內被淘汰。但是隨之而來的一個問題就是在一定的時間視窗內緩存的數據和實際數據不一致,比如設置 10 秒鐘過期時間,但是在這 10 秒之內業務又寫入了數據,那麼返回就不應該為空值了,所以還要考慮數據一致的問題,解決方法很簡單,利用消息系統或者主動更新的方式清除掉緩存中的數據即可。
布隆過濾器
- 1970 年布隆提出了一種布隆過濾器的演算法,用來判斷一個元素是否在一個集合中。這種演算法由一個二進位數組和一個 Hash 演算法組成。
- 具體的演算法思想這裡不再詳細解釋了,如有不瞭解的可以看陳某上一篇文章大白話布隆過濾器,又能和麵試官扯皮了~。
- 解決緩存穿透的大致思想:在訪問緩存層和存儲層之前,可以通過定時任務或者系統任務來初始化布隆過濾器,將存在的 key 用布隆過濾器提前保存起來,做第一層的攔截。例如:一個推薦系統有 4 億個用戶 id, 每個小時演算法工程師會根據每個用戶之前歷史行為計算出推薦數據放到存儲層中, 但是最新的用戶由於沒有歷史行為, 就會發生緩存穿透的行為, 為此可以將所有推薦數據的用戶做成布隆過濾器。 如果布隆過濾器認為該用戶 id 不存在, 那麼就不會訪問存儲層, 在一定程度保護了存儲層。此時的結構如下圖:
- 當然布隆過濾器的假陽性的存在導致了誤判率,但是我們可以儘量的降低誤判率,一個解決方案就是:使用多個 Hash 演算法為元素計算出多個 Hash 值,只有所有 Hash 值對應的數組中的值都為 1 時,才會認為這個元素在集合中。
- 這種方法適用於
數據命中不高
、數據相對固定
、實時性低
(通常是數據 集較大
)的應用場景,代碼維護較為複雜,但是緩存空間占用少。為什麼呢?因為布隆過濾器不支持刪除元素,一旦數據變化,並不能及時的更新布隆過濾器。
兩種方案對比
- 兩種方案各有優缺點,具體使用哪種方案還是要根據業務場景和系統體量來定。具體的區別如下表:
方案 | 適用場景 | 維護成本 |
---|---|---|
緩存對象 | 1. 數據命中不高 2. 數據頻繁變化,實時性高 | 代碼維護點單、需要過多的緩存空間,數據一致性需要自己實現 |
布隆過濾器 | 1. 數據命中不高 2.數據相對固定,實時性低 | 代碼維護複雜、緩存空間占用少 |
總結
- 至此,如何解決緩存穿透的問題已經介紹完了,覺得寫得不錯的,有所收穫的朋友,點點在看,分享關註一波。
- 最近不少讀者留言陳某希望我多發一些面試題,這幾天正好在整理大廠面試常問的面試題,後面會陸續發佈,已經發佈的面試題有兩篇,分別是【弔打面試官】Mysql 大
- 廠高頻面試題!!!、Redis 高頻面試題及答案。關註陳某,每天都會有面試題更新!!!