Redis之緩存擊穿、穿透、雪崩問題及處理
一、緩存擊穿
(一)概念
某個熱點 Key 過期的瞬間,大量并發(fā)請求同時打到數據庫,導致數據庫壓力瞬間飆升,甚至被打崩。
大量并發(fā)請求 ---> 訪問同一個熱點 key
↓
這個 key 正好過期
↓
所有請求同時繞過 Redis 訪問數據庫
↓
DB 瞬間壓力過大(被打爆)(二)緩存擊穿的后果
1. 數據庫壓力瞬間飆升
- 熱點 Key 過期后,大量并發(fā)請求直接落到數據庫
- 數據庫瞬間承受超高并發(fā)請求,CPU、IO 占用飆升
2. 系統(tǒng)性能下降
- 數據庫響應變慢 → 接口響應延遲增加
3. 請求可能超時或失敗
- 高并發(fā)下,整體系統(tǒng)吞吐量下降
4. 可能觸發(fā)雪崩效應
- 一個熱點 Key 擊穿導致數據庫壓力過大
- 可能影響其他業(yè)務請求
- 形成連鎖反應,多個 Key 失效 → 系統(tǒng)整體性能下降
5. 運維風險增加
- 數據庫連接耗盡、事務阻塞
- CPU/內存占用過高 → 可能導致服務宕機
- 需要緊急干預,影響業(yè)務連續(xù)性
(三)觸發(fā)條件
1. 熱點 Key(高訪問頻率)
概念:熱點 Key 是指在短時間內被大量請求訪問的數據。
特征:訪問量遠高于其他普通 Key,可能占據系統(tǒng)絕大部分流量。
具體示例:
- 電商網站的某個秒殺商品庫存信息
- 熱門文章或新聞詳情頁
- 排行榜數據,如“本周最熱商品 Top 10”
為什么關鍵:只有熱點數據過期,才會有大量請求瞬間打到數據庫,引發(fā)壓力。
2. Key 過期或被淘汰
概念:緩存中存放的 Key 可能由于以下原因失效:
(1)TTL 到期:設置了過期時間,到時間就失效
(2)手動刪除:開發(fā)或運維手動清理緩存
(3)內存淘汰:Redis 達到內存上限,根據 LRU/LFU 策略淘汰 Key
具體示例:
- 設置 SET product_123 100 EX 60,60 秒后過期
- 服務器更新產品信息,刪除緩存強制刷新
- Redis 內存滿了,大對象或低頻 Key 被淘汰
為什么關鍵:Key 過期或消失后,下一次請求會直接落到數據庫,這才是擊穿的直接觸發(fā)點。
3. 高并發(fā)訪問
概念:短時間內大量請求同時訪問同一個 Key。
特征:瞬時并發(fā)量遠高于數據庫處理能力。
具體示例:
- 秒殺活動開始時,幾千甚至上萬用戶同時訪問同一商品庫存
- 熱點文章推送后,瞬間大量用戶點擊訪問
為什么關鍵:如果并發(fā)量很小,即使 Key 過期,數據庫也能承受壓力;只有高并發(fā)訪問,才會引發(fā)真正的緩存擊穿。
4. 少了任意一個條件,就不會發(fā)生緩存擊穿
解釋:
- 熱點 Key 不存在 → 就算并發(fā)很高,也不會擊穿數據庫(普通 Key 的請求量本來就?。?/li>
- Key 沒有過期或被淘汰 → 緩存命中,所有請求都打到 Redis,不會落到數據庫
- 高并發(fā)訪問 不存在 → 即使 Key 過期,也只有少量請求訪問數據庫,數據庫能輕松承受
- 所以 三個條件必須同時滿足,緩存擊穿才會發(fā)生。
(四)典型場景
1. 熱門商品詳情
場景描述:
- 電商平臺的秒殺商品或促銷商品在短時間內被大量用戶訪問
- 每個用戶都要查詢庫存、價格、折扣等信息
為什么容易擊穿:
- 熱點 Key:這個商品的緩存是熱點,因為秒殺活動期間訪問量極高
- Key 過期或緩存未命中:TTL 到期,或者緩存被手動刷新
- 高并發(fā)請求:幾千甚至上萬用戶同時訪問數據庫查詢庫存
技術后果:
- 數據庫瞬時壓力飆升
- 查詢延遲增加 → 可能出現秒殺失敗或頁面崩潰
2. 排行榜或統(tǒng)計數據
場景描述:
- 熱門文章閱讀量、音樂/視頻播放排行榜、實時交易額統(tǒng)計
- 大量用戶同時查詢“Top N”數據
為什么容易擊穿:
- 熱點 Key:排行榜數據被頻繁訪問
- Key 過期:排行榜緩存每隔一段時間刷新一次,TTL 到期或邏輯過期
- 高并發(fā)訪問:緩存過期瞬間,所有請求直接打數據庫計算排行
技術后果:
- 數據庫需要進行復雜聚合計算,CPU/IO 占用高
- 多個 Key 可能同時被訪問 → 加劇系統(tǒng)壓力
3. 系統(tǒng)配置或元數據
場景描述:
- 經常查詢的基礎信息,例如用戶角色權限、地區(qū)列表、字典表數據
- 訪問頻率高,但數據量相對固定
為什么容易擊穿:
- 熱點 Key:這些 Key 被多次訪問
- Key 過期或被刷新:配置更新或 TTL 到期
- 高并發(fā)訪問:短時間內多個服務/用戶請求這些基礎數據
技術后果:
- 一旦緩存失效,基礎數據請求直接打數據庫
- 影響整個業(yè)務鏈條的正常訪問
(五)根本原因
1. Redis 只是緩存,并非數據庫防護墻
- Redis 的主要作用是加速數據訪問,減少數據庫壓力
- Redis 并不會阻止數據庫本身被訪問
- 一旦緩存未命中(Key 過期或被淘汰),請求就直接打數據庫
技術點:緩存只是讀寫加速層,它不存儲業(yè)務邏輯約束,也不限制請求流量
2. 高并發(fā)請求集中在失效 Key 上
- 當一個熱點 Key 過期或被刪除,瞬間所有訪問這個 Key 的請求都會落到數據庫
- 數據庫承受能力有限,如果瞬時 QPS 超過數據庫峰值 → 查詢排隊
技術點:
- Redis hit rate 高 → 大部分請求在緩存層就被攔截
- Key 失效瞬間 → 緩存失效窗口,hit rate 突然變低 → DB 瞬時壓力飆升
3. 數據庫無法承受大量并發(fā)請求
數據庫在高并發(fā)下可能出現:
(1)CPU 飽和 → 查詢速度下降
(2)IO 瓶頸 → 磁盤或網絡訪問慢
(3)連接耗盡 → 數據庫拒絕新連接
(3)事務阻塞 → 并發(fā)寫操作阻塞其他請求
結果:接口響應變慢、請求超時,甚至數據庫宕機
技術點:緩存擊穿不是 Redis 的問題,而是熱點數據失效 + 數據庫瞬時承載能力不足的系統(tǒng)問題
(六)解決策略
1. 熱點 Key 永不過期 / 邏輯過期
思路:
- 對真正熱點的數據,不設置 TTL,讓緩存一直存在
- 或者使用“邏輯過期”,即緩存中記錄一個過期時間,但讀取時仍然可以返回舊數據,同時后臺異步刷新
技術實現:
- 永不過期:SET key value 不設置 EX 參數
- 邏輯過期:緩存結構如 {value: ..., expireTime: 1234567890}
- 讀取時判斷 expireTime 是否過期
- 如果過期,后臺線程異步更新緩存,用戶仍然能讀取舊值
適用場景:
熱門商品、排行榜、系統(tǒng)配置、常用字典數據
2. 互斥鎖 / 單線程加載
思路:
- 當緩存失效時,只有一個線程去加載數據庫,其它線程等待緩存更新,避免高并發(fā)同時打數據庫
技術實現(Java 示例):
String cache = redis.get(key);
if (cache == null) {
if (tryLock(key + "_lock")) { // 嘗試獲取鎖
String dbData = queryFromDB(); // 查詢數據庫
redis.set(key, dbData, 60); // 回寫緩存
unlock(key + "_lock"); // 釋放鎖
return dbData;
} else {
Thread.sleep(50); // 等待一段時間
return redis.get(key); // 重試讀取緩存
}
}
return cache;適用場景:
秒殺活動、熱點 Key 高并發(fā)訪問場景
3. 緩存預熱 + 定時刷新
思路:
- 系統(tǒng)啟動或緩存即將過期時提前加載熱點數據到緩存
- 避免緩存失效瞬間出現大量請求落到數據庫
技術實現:
- 緩存預熱:系統(tǒng)啟動時讀取數據庫,將熱點 Key 加入 Redis
- 定時刷新:使用定時任務或后臺線程,提前更新即將過期的 Key
適用場景:
- 系統(tǒng)啟動后的熱門數據
- 高頻訪問的排行榜或統(tǒng)計數據
4. 降級處理 / 限流
思路:
- 當緩存擊穿瞬間,可以對部分請求做降級或限流,保護數據庫
- 部分請求直接返回默認值或提示稍后再試
技術實現:
- 使用限流器(如令牌桶、漏桶算法)限制數據庫訪問頻率
- 對熱點查詢返回緩存的舊值或默認值
- 結合互斥鎖或邏輯過期,提高系統(tǒng)容錯能力
適用場景:
- 秒殺活動、熱門接口請求暴增場景
- 保護數據庫穩(wěn)定,保證系統(tǒng)可用性
(七)優(yōu)化角度
1. 熱點識別
概念:先識別哪些 Key 是真正的熱點數據(訪問頻率高、壓力大的 Key),針對這些 Key 才做特殊處理。
具體做法:
(1)在 Redis 中記錄訪問頻率或使用監(jiān)控統(tǒng)計訪問量
(2)根據訪問量排序,識別訪問量大的 Key 為熱點
(3)對熱點 Key 可采取“永不過期”或“邏輯過期 + 異步刷新”等策略
作用:
- 只針對真正的熱點做優(yōu)化,避免資源浪費
- 降低緩存擊穿風險
2. TTL 差異化
概念:給不同 Key 設置不同過期時間,避免大量熱點 Key 同時過期。
具體做法:
- 對普通 Key 設置短 TTL
- 對熱點 Key 設置長 TTL 或邏輯過期
- 可以給相似熱點 Key 設置錯開 TTL,避免同時失效
作用:
- 避免多個熱點 Key 同時過期 → 大量請求同時打數據庫
- 平滑系統(tǒng)壓力
3. 分布式鎖 / 單點加載
概念:緩存失效時,保證只有一個請求去訪問數據庫加載數據,其他請求等待或重試緩存。
具體做法:
- 使用 Redis 的 SETNX 或 Redisson 提供的分布式鎖
- 第一個請求獲取鎖去加載數據庫
- 其他請求等待鎖釋放或讀取緩存
作用:
- 避免高并發(fā)請求同時擊穿數據庫
- 控制數據庫瞬時壓力
示例流程:
請求1 → 獲取鎖 → 查詢 DB → 回寫緩存 → 釋放鎖 請求2 → 獲取鎖失敗 → 等待 / 重試緩存 請求3 → 同上
4. 邏輯過期 + 異步刷新
概念:緩存中存儲數據的邏輯過期時間,而不是直接讓 Key 過期。
具體做法:
(1)緩存結構:{value: ..., expireTime: timestamp}
(2)讀取時判斷 expireTime 是否過期
- 未過期 → 直接返回緩存
- 已過期 → 后臺線程異步刷新緩存,前端仍返回舊值
(3)異步刷新完成后更新緩存
作用:
- 用戶請求不會直接落到數據庫
- 避免緩存失效瞬間擊穿數據庫
- 保證系統(tǒng)高并發(fā)下可用性
(八)注意事項
1. 分布式系統(tǒng)注意點
- 在多節(jié)點或微服務環(huán)境下,如果采用 分布式鎖 或 邏輯過期刷新,需要保證跨節(jié)點的一致性。
- 可使用成熟的工具或框架實現分布式鎖,例如 Redisson、ZooKeeper 或 Etcd。
- 這樣可以避免多個節(jié)點同時刷新緩存,防止數據庫壓力再次集中。
2. 數據一致性問題
- 使用 邏輯過期 + 異步刷新 時,可能會在短時間內返回過期的舊數據(臟數據)。
- 需要根據業(yè)務可容忍度判斷是否可以接受這種短暫的不一致,例如:排行榜、商品瀏覽量等統(tǒng)計類數據通常允許短暫延遲。
- 對于對實時性要求高的關鍵業(yè)務,需額外設計一致性策略。
3. 限流/降級策略的組合使用
在高并發(fā)場景下,單獨的策略可能不足以完全保護數據庫。
推薦組合方案:邏輯過期 + 分布式鎖 + 限流/降級
這樣可以確保:
- 請求不會直接擊穿數據庫
- 緩存刷新有序
- 系統(tǒng)在瞬時高并發(fā)下仍保持可用性
二、緩存穿透
(一)概念
緩存穿透指的是 查詢一個根本不存在的數據時,請求會繞過緩存直接打到數據庫,如果這種請求量很大,會導致數據庫壓力急劇增加。
- 核心:請求的數據在緩存和數據庫中都不存在
- 表現:緩存永遠無法命中,數據庫承受全部請求
請求數據A(不存在)
↓
Redis查詢,未命中
↓
直接訪問數據庫
↓
數據庫也沒有該數據
↓
返回結果給用戶
特點:
- 數據不存在 → 每次請求都要查詢數據庫
- 緩存沒有命中 → 每次都落到數據庫
- 高并發(fā)下可能造成數據庫壓力過大
(二)觸發(fā)原因
1. 用戶惡意請求
- 惡意刷接口,隨機生成不存在的 ID 請求
- 例如:/product/999999999,數據庫中根本沒有
2. 程序缺陷或參數錯誤
- 前端或接口調用傳錯參數,導致請求不存在的數據
- 例如:用戶傳了非法商品 ID 或拼寫錯誤的關鍵字
3. 緩存未處理空結果
- 查詢不存在的數據,緩存層沒有保存空對象
- 每次請求都走數據庫 → 穿透
(三)典型場景
1. 商品查詢接口
- 用戶請求不存在的商品 ID
- 沒有緩存空對象 → 每次都打數據庫
2. 用戶登錄或注冊校驗接口
- 查詢不存在的用戶名或手機號
- 高并發(fā)時可能形成數據庫壓力
3. 通用搜索或統(tǒng)計接口
- 查詢歷史不存在的記錄或隨機關鍵字
- 數據庫承載能力下降
(四)可能后果
1. 數據庫壓力增加
- 大量不存在的數據請求直接落到數據庫
- CPU、IO 占用增加
2. 接口響應變慢 / 超時
- 高并發(fā)下數據庫響應慢,接口可能返回超時
3. 系統(tǒng)可用性降低
- 數據庫過載 → 其他正常請求也受影響
4. 潛在安全風險
- 惡意請求可能導致拒絕服務(DoS)攻擊
(五)解決策略
1. 緩存空對象(Null Cache)
- 將數據庫查詢不存在的數據也緩存起來(空對象或特殊標記)
- 設置較短 TTL 避免緩存無限膨脹
示例:
String cache = redis.get(key);
if (cache != null) {
return cache.equals("NULL") ? null : cache;
} else {
String dbData = queryFromDB();
if (dbData == null) {
redis.set(key, "NULL", 60); // 緩存空對象
return null;
} else {
redis.set(key, dbData, 3600);
return dbData;
}
}2. 布隆過濾器(Bloom Filter)
- 在請求到達緩存/數據庫之前,用布隆過濾器快速判斷 key 是否存在
- 不存在的請求直接攔截,避免落到數據庫
特點:
- 支持海量數據
- 允許少量誤判(存在概率誤判為存在,但不會漏判)
- 場景:熱門商品 ID、用戶 ID 等高頻接口
3. 接口層校驗 / 參數校驗
- 對傳入請求參數做合法性校驗
- 非法 ID、空 ID、格式錯誤的請求直接拒絕
4. 限流和防刷策略
- 對接口增加限流、頻率限制
- 防止惡意請求或高并發(fā)重復查詢不存在數據
(六)進一步優(yōu)化與注意事項
1. 分布式環(huán)境注意點
- 如果系統(tǒng)是多節(jié)點或微服務架構,需要保證布隆過濾器或緩存空對象在各節(jié)點的一致性。
- 可以使用 集中式布隆過濾器 或分布式緩存同步策略,避免不同節(jié)點出現數據不一致。
2. 空對象緩存策略優(yōu)化
- 緩存空對象時需要控制 TTL,避免占用過多緩存資源。
- 對于熱門但不存在的數據,可以適當加長 TTL;對于隨機或惡意請求的數據,TTL 設置短一些即可。
- 結合日志監(jiān)控,發(fā)現頻繁穿透的 Key,可進一步處理或封禁請求源。
3. 結合限流與防刷策略
在高并發(fā)或惡意請求場景,單靠緩存空對象或布隆過濾器可能不足。
可以使用 限流 + 防刷,例如:
- 限制接口每秒請求次數
- 對異常頻繁請求 IP 或用戶進行封禁或驗證碼驗證
- 結合緩存策略可以更有效保護數據庫穩(wěn)定性。
4. 監(jiān)控與報警
- 對緩存命中率、數據庫查詢量進行監(jiān)控
- 當發(fā)現緩存命中率下降、數據庫異常增多時,觸發(fā)告警
- 幫助運維及時發(fā)現緩存穿透攻擊或異常請求
三、緩存雪崩
(一)概念
緩存雪崩指的是大量緩存同時失效,導致大量請求直接打到數據庫,使數據庫瞬間承受超高壓力,可能導致系統(tǒng)不可用。
- 核心:大量緩存同時過期或失效
- 表現:數據庫壓力驟增,接口響應延遲或失敗
流程示意:
大量熱點Key同時過期
↓
緩存全部未命中
↓
請求全部打到數據庫
↓
數據庫承受高壓
↓
接口響應變慢或宕機
特點:
- 大量 Key 同時失效 → 大量請求集中落到數據庫
- 高并發(fā)下數據庫無法承受 → 系統(tǒng)可能整體不可用
不同于緩存擊穿:緩存雪崩可能涉及 多個 Key,而擊穿通常是 單個熱點 Key
(二)觸發(fā)原因
1. 大量 Key TTL 相同
- 系統(tǒng)初始化時給緩存設置了統(tǒng)一過期時間,例如都 1 小時
- 到期瞬間,大量請求同時訪問數據庫
2. Redis 宕機或重啟
- Redis 服務不可用 → 緩存全部失效
- 所有請求直接落到數據庫
3. 緩存淘汰策略觸發(fā)
- 當 Redis 內存不足,根據 LRU/LFU 策略淘汰大量 Key
- 瞬時失效 → 請求直接訪問數據庫
(三)典型場景
1. 秒殺活動或促銷活動
- 活動前預熱緩存,設置統(tǒng)一過期時間
- 活動開始或 TTL 到期 → 大量商品緩存同時過期
- 大量請求直接打數據庫 → 宕機風險
2. 系統(tǒng)啟動或 Redis 重啟
- 系統(tǒng)重啟或 Redis 重啟 → 緩存清空
- 熱門 Key 未預熱 → 請求直接落數據庫
3. 大規(guī)模緩存失效
- 一些批量刷新或緩存清理操作
- 例如定時清理排行榜、統(tǒng)計數據
- 多個 Key 同時失效 → 請求瞬間集中
(四)可能后果
1. 數據庫瞬時壓力飆升
- CPU、IO、連接數暴漲
2. 接口響應延遲或失敗
- 請求排隊 → 用戶體驗差
3. 系統(tǒng)可用性下降
- 多個業(yè)務模塊同時受影響 → 鏈式反應
4. 觸發(fā)緩存擊穿或雪崩放大
- 一個 Key 的高并發(fā)擊穿 + 大量 Key 同時過期 → 系統(tǒng)雪崩
(五)解決策略
1. 緩存過期時間錯開 / 隨機化 TTL
- 給 Key TTL 增加隨機偏移量,避免大量 Key 同時過期
- 例如:設置 TTL = 3600 ± random(0~300) 秒
2. 緩存預熱(Cache Preload)
- 系統(tǒng)啟動或緩存即將失效前,提前加載熱點數據到緩存
- 避免大量請求落到數據庫
3. 互斥鎖或單線程加載
- 當緩存失效時,控制只有一個請求訪問數據庫,其它請求等待
- 避免高并發(fā)直接打數據庫
4. 降級與限流
- 對部分請求返回默認值或提示稍后重試
- 使用限流器控制訪問數據庫的速率
5. 多級緩存 / 本地緩存 + Redis
- 本地緩存存熱點數據 → Redis 失效前可以命中本地緩存
- 降低對 Redis 和數據庫瞬時壓力
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

