文章目錄
- 緩存穿透
- 什么是緩存穿透?
- 緩存穿透情況的處理流程是怎樣的?
- 緩存穿透的解決辦法
- 緩存無效 key
- 布隆過濾器
- 緩存雪崩
- 什么是緩存雪崩?
- 緩存雪崩的解決辦法
- 緩存擊穿
- 什么是緩存擊穿?
- 緩存擊穿的解決辦法
- 區別對比
在如今的開發中,使用緩存中間件 Redis 已經成為一項很廣泛的技術,Redis 的高性能大大優化了我們的服務器性能,緩解了在高并發的情況下服務器的壓力。它基于緩存的形式,在內存中保存數據,減少對磁盤的 IO 操作。然而盡管 Redis 有著很多的優點,但仍然有三朵烏云漂浮在 Redis 的上空:穿透,擊穿,雪崩。
緩存穿透
什么是緩存穿透?
請求的數據既不在緩存中,也不在數據庫中(如惡意攻擊、非法ID查詢)。
緩存穿透說簡單點就是大量請求的 key 根本不存在于緩存中,導致請求直接到了數據庫上,根本沒有經過緩存這一層。舉個例子:某個黑客故意制造我們緩存中不存在的 key 發起大量請求,導致大量請求落到數據庫。
緩存穿透情況的處理流程是怎樣的?
用戶的請求最終都要跑到數據庫查詢一遍。
緩存穿透的解決辦法
最基本的就是首先做好參數校驗,一些不合法的參數請求直接拋出異常信息返回給客戶端。比如查詢的數據庫 id 不能小于 0、傳入的郵箱格式不對的時候直接返回錯誤消息給客戶端等等。
緩存無效 key
如果緩存和數據庫都查不到某個 key 的數據就寫一個到 Redis 中去并 設置過期時間,具體命令如下: SET key value EX 10086
。這種方式 可以解決請求的 key 變化不頻繁的情況,如果黑客惡意攻擊,每次構建不同的請求 key,會導致 Redis 中緩存大量無效的 key 。很明顯,這種方案并不能從根本上解決此問題。如果非要用這種方式來解決穿透問題的話,盡量將無效的 key 的過期時間設置短一點比如 1 分鐘。
另外,這里多說一嘴,一般情況下我們是這樣設計 key 的: 表名:列名:主鍵名:主鍵值
。
如果用 Java 代碼展示的話,差不多是下面這樣的:
public Object getObjectInclNullById(Integer id) {// 從緩存中獲取數據Object cacheValue = cache.get(id);// 緩存為空if (cacheValue == null) {// 從數據庫中獲取Object storageValue = storage.get(key);// 緩存空對象cache.set(key, storageValue);// 如果存儲數據為空,需要設置一個過期時間(300秒)if (storageValue == null) {// 必須設置過期時間,否則有被攻擊的風險cache.expire(key, 60 * 5);}return storageValue;}return cacheValue;
}
布隆過濾器
更具體的布隆過濾器原理以及實現可以查看Redis—布隆過濾器-CSDN博客
布隆過濾器是一個非常神奇的數據結構,通過它我們可以非常方便地判斷一個給定數據是否存在于海量數據中。我們需要的就是判斷 key 是否合法,有沒有感覺布隆過濾器就是我們想要找的那個“人”。
具體是這樣做的:把所有可能存在的請求的值都存放在布隆過濾器中,當用戶請求過來,先判斷用戶發來的請求的值是否存在于布隆過濾器中。不存在的話,直接返回請求參數錯誤信息給客戶端,存在的話才會走下面的流程。
加入布隆過濾器之后的緩存處理流程圖如下。
但是,需要注意的是布隆過濾器可能會存在誤判的情況。總結來說就是: 布隆過濾器說某個元素存在,小概率會誤判。布隆過濾器說某個元素不在,那么這個元素一定不在。
為什么會出現誤判的情況呢? 我們還要從布隆過濾器的原理來說!
我們先來看一下,當一個元素加入布隆過濾器中的時候,會進行哪些操作:
- 使用布隆過濾器中的哈希函數對元素值進行計算,得到哈希值(有幾個哈希函數得到幾個哈希值)。
- 根據得到的哈希值,在位數組中把對應下標的值置為 1。
我們再來看一下,當我們需要判斷一個元素是否存在于布隆過濾器的時候,會進行哪些操作:
- 對給定元素再次進行相同的哈希計算;
- 得到值之后判斷位數組中的每個元素是否都為 1,如果值都為 1,那么說明這個值在布隆過濾器中,如果存在一個值不為 1,說明該元素不在布隆過濾器中。
然后,一定會出現這樣一種情況:不同的字符串可能哈希出來的位置相同。 (可以適當增加位數組大小或者調整我們的哈希函數來降低概率)
緩存雪崩
什么是緩存雪崩?
緩存雪崩描述的就是這樣一個簡單的場景:緩存在同一時間大面積的失效,后面的請求都直接落到了數據庫上,造成數據庫短時間內承受大量請求。 這就好比雪崩一樣,摧枯拉朽之勢,數據庫的壓力可想而知,可能直接就被這么多請求弄宕機了。
舉個例子:系統的緩存模塊出了問題比如宕機導致不可用。造成系統的所有訪問,都要走數據庫。
還有一種緩存雪崩的場景是:有一些被大量訪問數據(熱點緩存)在某一時刻大面積失效,導致對應的請求直接落到了數據庫上。 這樣的情況,有下面幾種解決辦法:
舉個例子 :秒殺開始 12 個小時之前,我們統一存放了一批商品到 Redis 中,設置的緩存過期時間也是 12 個小時,那么秒殺開始的時候,這些秒殺的商品的訪問直接就失效了。導致的情況就是,相應的請求直接就落到了數據庫上,就像雪崩一樣可怕。
緩存雪崩的解決辦法
針對 Redis 服務不可用的情況:
- 采用 Redis 集群,避免單機出現問題整個緩存服務都沒辦法使用。
- 限流,避免同時處理大量的請求。
針對熱點緩存失效的情況:
- 設置不同的失效時間比如隨機設置緩存的失效時間。
- 緩存永不失效。
緩存擊穿
什么是緩存擊穿?
緩存擊穿是指 ?某個熱點數據在緩存過期(失效)的瞬間?,大量并發請求直接穿透緩存,全部打到數據庫上,導致數據庫負載驟增甚至崩潰的現象。
緩存擊穿的解決辦法
- 互斥鎖:
原理?:當緩存失效時,只允許一個線程去查詢數據庫并重建緩存,其他線程等待鎖釋放后直接讀取新緩存。
?實現方式?:
- ?分布式鎖?(如 Redis 的
SETNX
或 Redisson 的RLock
)。 - ?本地鎖?(單機環境下可用
synchronized
或ReentrantLock
)。
- 永不過期:
原理?:緩存不設置過期時間,通過 ?異步線程定期更新緩存?(如定時任務或消息隊列觸發)。
?實現方式?:
- 緩存設置為永久有效。
- 通過后臺任務或事件驅動更新數據。
- 緩存預熱
?原理?:在系統啟動或低峰期,提前加載熱點數據到緩存,避免首次請求穿透。
?實現方式?:
- 統計歷史熱點數據(如 Top-N 訪問 Key)。
- 通過腳本或定時任務預加載緩存。
?適用場景?:
- 電商大促前預加載秒殺商品數據。
- 新聞類應用預加載頭條內容。
區別對比
維度 | 緩存穿透 | 緩存雪崩 | 緩存擊穿 |
---|---|---|---|
?觸發條件? | 請求的數據==?既不在緩存,也不在數據庫?==(如無效ID、惡意攻擊) | ?大量緩存同時失效?(如集體過期或緩存服務宕機) | ?單個熱點數據過期失效?(如秒殺商品緩存到期) |
?影響范圍? | 單個或多個無效數據請求 | ?全局性?(大量緩存失效,導致數據庫壓力驟增) | ?局部性?(僅某個熱點數據失效,但并發極高) |
?請求特征? | 大量==?無效請求?==(惡意或業務缺陷) | 大量==?有效請求?==因緩存失效直接訪問數據庫 | 大量==?有效請求?==集中訪問同一失效熱點數據 |
?解決方案? | 1. 布隆過濾器 2. 緩存空值 3. 接口鑒權 | 1. 隨機過期時間 2. 多級緩存 3. 熔斷降級 | 1. 互斥鎖 2. 邏輯過期 3. 永不過期策略 |
?典型場景? | 爬蟲惡意掃描不存在的ID | 緩存服務宕機或批量設置相同TTL | 微博熱搜、秒殺活動等高并發場景 |