緩存更新策略
在 Redis 緩存中,緩存的更新策略主要有**定期生成(定時更新)和實時生成(即時更新)**兩種方式。不同的策略適用于不同的業務場景,涉及性能、數據一致性和系統負載等方面的權衡。
1. 定期生成(定時更新)
是什么?
定期生成指的是按照固定的時間間隔,主動更新緩存,而不是在數據發生變化時立即更新。這種方式適用于數據變化不頻繁、對實時性要求不高的場景。
優點:
? 降低數據庫壓力:緩存可以批量更新,避免頻繁查詢數據庫。
? 提高查詢性能:查詢時直接讀取緩存,響應速度快。
? 數據一致性較好(相對于長期不更新的緩存):定期更新可以保證數據不會長期過時。
缺點:
? 數據可能不夠實時:在緩存下一次更新前,數據可能已經變化,但緩存仍然返回舊數據。
? 不適合高實時性業務:如果業務需要頻繁變更數據,定期更新可能導致緩存數據滯后。
? 可能會引起短時流量沖擊:如果所有緩存數據同時更新,可能會對數據庫造成瞬間壓力。
常見實現方式:
-
定時任務更新緩存(Time-based Refresh)
- 使用 Spring Task、Quartz、Crontab 等定時任務,每隔一段時間刷新緩存。
- 例如,每 10 分鐘更新一次緩存:
@Scheduled(fixedRate = 600000) // 每 10 分鐘執行一次 public void updateCache() {// 查詢數據庫并更新緩存List<Data> dataList = databaseService.getData();redisTemplate.opsForValue().set("cache:data", dataList); }
-
數據庫變更時觸發緩存更新(Database-triggered Refresh)
- 監聽 數據庫變更事件(MySQL Binlog、PostgreSQL 觸發器),檢測到數據變化后批量刷新緩存。
-
異步任務更新
- 使用消息隊列(Kafka、RabbitMQ)通知服務更新緩存,避免定時任務導致的瞬時數據庫壓力過大。
適用場景:
📌 統計數據、排行榜、熱門商品列表等(更新頻率較低,數據稍有延遲也無大問題)。
📌 日志分析、報表數據等(數據量大,但對實時性要求不高)。
2. 實時生成(即時更新)
是什么?
實時生成指的是數據發生變更時立即更新緩存,確保緩存數據始終是最新的。這種方式適用于對數據一致性要求高、變更較頻繁的場景。
優點:
? 數據實時性高:緩存的數據始終與數據庫保持一致,適用于高實時性需求的應用。
? 避免緩存不一致問題:數據庫變更后立即同步緩存,減少數據不匹配的情況。
缺點:
? 更新成本高:每次數據變更都需要更新緩存,可能會導致數據庫壓力增大。
? 可能導致緩存頻繁更新:對于高頻變更的數據,頻繁更新可能會導致 Redis 負載過重,甚至影響整體性能。
? 并發問題:多個并發請求可能會導致緩存不一致或緩存擊穿,需要加鎖或使用雙寫策略。
常見實現方式:
-
數據庫更新時主動更新緩存(Write-through Strategy)
- 在 **數據更新(新增、修改、刪除)**時,同時更新數據庫和緩存:
public void updateData(Data data) {databaseService.updateData(data); // 更新數據庫redisTemplate.opsForValue().set("cache:data:" + data.getId(), data); // 同步更新緩存 }
- 適用于數據變更不頻繁,且一致性要求較高的場景。
- 在 **數據更新(新增、修改、刪除)**時,同時更新數據庫和緩存:
-
緩存淘汰(Cache Eviction)
- 在數據庫更新后,刪除緩存,讓下一次查詢時重新加載數據:
public void updateData(Data data) {databaseService.updateData(data); // 更新數據庫redisTemplate.delete("cache:data:" + data.getId()); // 刪除緩存 }
- 適用于緩存數據不是熱點,數據變更后不需要立即被查詢的情況。
- 在數據庫更新后,刪除緩存,讓下一次查詢時重新加載數據:
-
訂閱數據庫變更(Event-based Strategy)
- 使用 消息隊列(Kafka、RabbitMQ) 或 Redis 訂閱/發布機制,監聽數據庫變更事件,變更后更新緩存。
-
分布式鎖(避免緩存并發寫入問題)
- 解決多個請求同時更新緩存導致數據不一致的問題:
RLock lock = redissonClient.getLock("cache:lock:data:" + data.getId()); try {if (lock.tryLock(5, TimeUnit.SECONDS)) {databaseService.updateData(data);redisTemplate.opsForValue().set("cache:data:" + data.getId(), data);} } finally {lock.unlock(); }
- 適用于高并發寫入場景,防止緩存同時被多個請求覆蓋。
- 解決多個請求同時更新緩存導致數據不一致的問題:
適用場景:
📌 訂單系統、支付系統、庫存管理等(數據必須實時更新,不能有延遲)。
📌 直播、彈幕系統(數據實時變化,需要確保一致性)。
總結:定期生成 vs. 實時生成
策略 | 定期生成(定時更新) | 實時生成(即時更新) |
---|---|---|
數據實時性 | 低(有一定延遲) | 高(數據庫更新即緩存更新) |
數據庫壓力 | 低(定期批量更新) | 高(頻繁更新緩存) |
緩存命中率 | 高(查詢時直接命中緩存) | 可能較低(某些情況需刪除緩存) |
適用場景 | 排行榜、統計數據、報表等 | 訂單、庫存、支付等高一致性業務 |
總結
- 定期生成(定時更新) 適用于數據變化不頻繁、對實時性要求不高的場景,如排行榜、日志分析等。
- 實時生成(即時更新) 適用于數據變化頻繁、對一致性要求高的場景,如支付、庫存、訂單管理等。
- 在實際應用中,可以結合兩種策略,例如:
- 定期更新 + 變更觸發更新:大部分數據定期刷新,關鍵數據實時更新。
- 讀時更新 + 寫時淘汰:查詢時自動更新緩存,寫入時刪除緩存,防止數據不一致。
合理選擇緩存更新策略,可以有效提升系統性能,降低數據庫壓力,并保證數據的一致性。
Redis 作為緩存,存儲空間有限,因此需要淘汰數據來保證新數據的存入。Redis 提供了多種緩存淘汰策略(Eviction Policy),用于決定哪些數據需要被刪除。下面介紹幾種常見的淘汰策略,包括它們的適用場景和優缺點。
緩存淘汰策略
1. 不淘汰策略
1.1 noeviction(拒絕寫入)
概念:
當 Redis 內存占滿時,不會刪除任何已有數據,而是直接返回錯誤,拒絕新的寫入請求。
適用場景:
- 適用于嚴格不能丟數據的場景,如任務隊列(消息隊列)、金融交易等。
- 適用于 Redis 作為純數據存儲而非緩存時。
優缺點:
? 數據不會被誤刪除,保證數據完整性。
? 可能導致寫入失敗,影響系統穩定性。
2. 基于 TTL(過期時間)的淘汰策略
2.1 volatile-lru(最近最少使用,TTL 限定)
概念:
- 只淘汰**設置了過期時間(TTL)**的鍵。
- 在這些鍵中,優先刪除最近最少使用(LRU, Least Recently Used)的數據。
適用場景:
- 適用于部分數據可丟棄的場景,比如 session、短期緩存數據。
- 適用于需要自動過期控制,但仍希望盡可能保留熱點數據的情況。
優缺點:
? 優先保留常用數據,減少緩存擊穿的概率。
? 如果大部分 key 沒有 TTL,可能導致 Redis 直接拒絕寫入(相當于 noeviction)。
2.2 volatile-ttl(優先淘汰即將過期的鍵)
概念:
- 只淘汰**設置了過期時間(TTL)**的鍵。
- 其中剩余壽命最短的鍵優先被刪除。
適用場景:
- 適用于對數據有明確的生命周期需求的業務,如訂單緩存、驗證碼緩存等。
優缺點:
? 優先刪除即將過期的數據,保證短期緩存的更新。
? 可能誤刪仍然有價值的熱點數據。
3. 基于數據訪問頻率的淘汰策略
3.1 allkeys-lru(全局最近最少使用)
概念:
- 無視 TTL,從所有鍵中(包括沒有設置 TTL 的鍵),優先淘汰最近最少使用的鍵。
適用場景:
- 適用于熱點數據更新頻繁的場景,如推薦系統、排行榜、搜索結果緩存等。
優缺點:
? 可以確保常用數據長期保留,提高緩存命中率。
? 如果熱點數據突然減少訪問,可能會被錯誤淘汰。
3.2 allkeys-random(全局隨機淘汰)
概念:
- 無視 TTL,在所有 key 中隨機刪除某些數據。
適用場景:
- 適用于緩存數據均勻訪問,不需要特定優先級的場景。
優缺點:
? 簡單高效,減少淘汰策略的計算開銷。
? 不夠智能,可能淘汰熱點數據,降低緩存命中率。
4. 基于數據訪問頻次的淘汰策略
4.1 volatile-lfu(基于訪問頻率,TTL 限定)
概念:
- 只淘汰設置了 TTL 的 key。
- 訪問次數最少的鍵優先被刪除(LFU, Least Frequently Used)。
適用場景:
- 適用于需要根據訪問次數保留數據的業務,如熱點文章緩存、用戶歷史記錄等。
優缺點:
? 能夠長期保留高頻訪問數據,淘汰低頻數據。
? 如果大部分數據沒有 TTL,可能導致 Redis 拒絕寫入(類似 noeviction)。
4.2 allkeys-lfu(全局最不常使用淘汰)
概念:
- 無視 TTL,從所有鍵中優先淘汰訪問次數最少的鍵。
適用場景:
- 適用于熱點數據訪問有明顯差異的情況,如新聞熱點推薦、熱門產品緩存等。
優缺點:
? 能保留長期熱點數據,提高緩存命中率。
? 短期熱點數據可能無法及時替換,導致數據更新滯后。
總結
策略 | 機制 | 適用場景 | 優點 | 缺點 |
---|---|---|---|---|
noeviction | 拒絕寫入 | 不能丟數據(消息隊列、金融) | 數據安全 | 容易寫滿導致錯誤 |
volatile-lru | 僅淘汰 TTL 數據,LRU | 需自動過期,保留熱點數據 | 減少緩存擊穿 | 僅適用于部分數據有 TTL |
volatile-ttl | 僅淘汰 TTL 數據,剩余壽命短的優先 | 訂單緩存、驗證碼 | 優先清理即將失效的緩存 | 可能誤刪熱點數據 |
allkeys-lru | 全局 LRU 淘汰 | 訪問頻率高的緩存(推薦系統) | 提高緩存命中率 | 可能誤刪突然冷卻的熱點數據 |
allkeys-random | 隨機淘汰 | 數據訪問均勻的緩存 | 計算開銷小 | 可能淘汰重要數據 |
volatile-lfu | 僅淘汰 TTL 數據,訪問最少的優先 | 需要根據訪問頻率保留數據 | 長期熱點數據保留 | 僅適用于有 TTL 的 key |
allkeys-lfu | 全局 LFU 淘汰 | 熱點明顯的數據(新聞、直播) | 緩存命中率高 | 短期熱點更新慢 |
如何選擇淘汰策略?
1. 數據不能丟失(消息隊列、金融)
? noeviction(拒絕寫入)
2. 僅淘汰過期數據(業務數據自動失效)
? volatile-lru(保留熱點)
? volatile-ttl(優先清理快過期數據)
3. 需要智能保留高頻訪問數據
? allkeys-lru(最近最少使用淘汰)
? allkeys-lfu(最少使用淘汰)
4. 訪問數據均勻,不關心淘汰順序
? allkeys-random(隨機刪除)
5. 業務需要權衡 LRU 和 LFU
- 短期熱點多,選 LRU
- 長期熱點多,選 LFU
結論
- 如果數據有 TTL,且希望優先淘汰冷數據,選 volatile-lru / volatile-lfu。
- 如果所有數據都可以被淘汰,選 allkeys-lru / allkeys-lfu。
- 如果只允許寫滿后拒絕寫入,選 noeviction。
- 如果對淘汰規則無特別要求,選 allkeys-random。
正確選擇淘汰策略,可以有效提高緩存命中率,降低數據庫壓力,保障系統穩定性。
常見緩存問題
在 Redis 中,緩存預熱、緩存穿透、緩存雪崩和緩存擊穿是常見的緩存問題。下面分別描述它們的概念及解決方案:
1. 緩存預熱(Cache Warming)
是什么?
緩存預熱是指在系統啟動或運行之前,提前將熱點數據加載到緩存中,以減少數據庫的查詢壓力,提高系統訪問速度。
如何解決?
- 手動加載:在服務啟動時,手動將熱點數據寫入緩存。
- 定時刷新:通過定時任務(如 Spring Task、Quartz 等)定期加載熱點數據到緩存。
- 數據變更同步:監聽數據庫更新(如 MySQL binlog、Redis 訂閱發布機制),在數據變化時同步更新緩存。
- 批量加載:使用 Redis 的
pipeline
或mset
命令批量寫入緩存,提高加載效率。
2. 緩存穿透(Cache Penetration)
是什么?
緩存穿透指的是大量請求查詢不存在的數據,導致每次請求都要查詢數據庫,緩存完全失效,給數據庫帶來巨大壓力。
如何解決?
- 緩存空值:如果查詢的數據不存在,可以將空值(如
null
或{}
)存入緩存,并設置較短的過期時間,避免重復查詢數據庫。 - 布隆過濾器(Bloom Filter):使用布隆過濾器提前判斷某個 key 是否可能存在,如果一定不存在,則直接返回,不查詢數據庫。
- 參數校驗:在請求層對參數進行校驗,避免無效請求進入系統。
- 限流與黑名單:對異常請求 IP 進行封禁或限流,避免惡意攻擊。
3. 緩存雪崩(Cache Avalanche)
是什么?
緩存雪崩指的是大量緩存同時失效,導致短時間內大量請求直接打到數據庫,造成數據庫壓力激增,甚至宕機。
如何解決?
- 緩存過期時間隨機化:為緩存設置不同的過期時間,避免大量緩存同時失效,例如使用
TTL = 基礎時間 ± 隨機時間
。 - 熱點數據提前預加載:在緩存即將過期前,主動刷新緩存,保證熱點數據始終可用。
- 雙層緩存:使用 Redis + 本地緩存(如 Caffeine、Guava Cache),降低對 Redis 的依賴。
- 流量削峰:
- 限流:使用限流算法(如令牌桶、漏桶)限制訪問速率。
- 降級:當數據庫壓力過大時,返回默認值或降級處理。
4. 緩存擊穿(Cache Breakdown)
是什么?
緩存擊穿指的是某個熱點 key 突然失效,導致大量并發請求直接打到數據庫,造成數據庫短時間內壓力劇增。
如何解決?
- 設置熱點數據永不過期:對于熱點數據,直接不設置過期時間,而是由業務邏輯主動更新緩存。
- 互斥鎖(分布式鎖):
- 當緩存失效時,多個請求只允許一個線程查詢數據庫并更新緩存,其他線程等待緩存更新完成后再讀取。
- 具體實現:使用
SETNX
(Redis 分布式鎖) 或 Redisson。
- 提前刷新緩存:
- 通過異步線程提前更新即將過期的熱點緩存,防止突然失效。
- 例如:使用
Redis + 過期監聽
,在 key 即將過期前主動更新緩存。
總結
問題 | 現象 | 解決方案 |
---|---|---|
緩存預熱 | 緩存剛啟動時,沒有數據 | 手動加載、定時刷新、監聽數據變更 |
緩存穿透 | 查詢的 key 在數據庫中不存在,每次都查數據庫 | 緩存空值、布隆過濾器、參數校驗、黑名單 |
緩存雪崩 | 大量 key 同時失效,數據庫壓力激增 | 過期時間隨機化、雙層緩存、限流、降級 |
緩存擊穿 | 某個熱點 key 失效,大量請求打到數據庫 | 熱點數據永不過期、分布式鎖、提前刷新 |
這四個緩存問題都是分布式系統中必須重點關注的,合理的緩存策略可以有效提升系統的性能和穩定性。