緩存更新策略
文章目錄
- 緩存更新策略
- 一、策略對比
- 二、常見的緩存更新策略
- 三、如何選擇策略
- 四、實際應用示例
- 五、使用 Cache-Aside + TTL 的方式,實現緩存商鋪信息詳情
- 1.引入StringRedisTemplate
- 2.將查詢商鋪信息加入緩存
- 3.更新商鋪信息時移除緩存
- 總結
- 六、注意事項
一、策略對比
內存淘汰 | 超時剔除 | 主動更新 | |
---|---|---|---|
說明 | 不用自己維護, 利用Redis的內存淘汰機制, 當內存不足時自動淘汰部分數據。下次查詢時更新緩存。 | 給緩存數據添加TTL時間, 到期后自動刪除緩存。 下次查詢時更新緩存。 | 編寫業務邏輯, 在修改數據庫的同時,更新緩存 |
一致性 | 差 | 一般 | 好 |
維護成本 | 無 | 低 | 高 |
業務場景:
- 低一致性需求:使用內存淘汰機制。例如商鋪類型的查詢緩存
- 高一致性需求:主動更新,并以超時剔除作為兜底方案。例如店鋪詳情查詢的緩存
二、常見的緩存更新策略
Cache Aside Pattern
(旁路緩存模式):由緩存的調用者,在更新數據庫的同時更新緩存。- 原理:
- 讀操作:先查詢緩存,若命中則直接返回;未命中則從數據庫加載數據并寫入緩存。
- 寫操作:先更新數據庫,再刪除緩存(或更新緩存)。
- 優點:簡單易實現,適用于大多數場景。
- 缺點:可能存在短暫的數據不一致(如并發寫入導致臟數據)。
- 場景:讀多寫少、對一致性要求不高的場景。
- 原理:
Read / Write Through Pattern
(讀穿/寫穿):緩存與數據庫整合為一個服務,由服務來維護一致性。調用者調用該服務,無需關心緩存一致性問題。- 讀穿透(Read-Through):
- 當緩存未命中時,由緩存服務自動從數據庫加載數據并回填緩存 。
- 寫穿透(Write-Through):
- 數據更新時,同時寫入緩存和數據庫,保證兩者同步。
- 優點:對業務透明,數據一致性高。
- 缺點:寫操作延遲較高,依賴緩存服務的可靠性。
- 場景:強一致性要求的場景。
- 讀穿透(Read-Through):
Write Behind Caching Pattern
(異步寫回):調用者只操作緩存,由其他線程異步地將緩存數據持久化到數據庫,保證最終一致性。- 原理:
- 寫操作僅更新緩存,異步批量將數據持久化到數據庫。
- 優點:寫性能極高,減少數據庫壓力。
- 缺點:存在數據丟失風險(如系統崩潰時為同步完成的數據)。
- 場景:允許短暫不一致、寫密集型場景(如日志系統)。
- 原理:
- 被動更新(TTL刷新)
- 原理:
- 為緩存設置過期時間(Time-To-Live,TTL),到期后自動失效并從數據庫重新加載。
- 優點:實現簡單,避免長期臟數據。
- 缺點:無法主動控制更新實際,可能出現緩存雪崩(大量緩存同時失效)。
- 場景:數據變動頻率較低的場景。
- 原理:
- 主動更新(事件驅動)
- 原理:
- 當數據庫數據變更時(如通過消息隊列或出發期),主動通知應用層更新或刪除對應緩存。
- 優點:實時性高,減少無效查詢。
- 缺點:依賴外部系統協同,復雜度較高。
- 場景:分布式系統中強一致性要求的場景(如電商庫存更新)。
- 原理:
注意:在使用Cache Aside Pattern
方式的工程中,操作緩存和數據庫時有三個問題需要考慮:
- 刪除緩存還是更新緩存?
- 更新緩存:每次更新數據庫都更新緩存,無效寫操作較多
- 刪除緩存【推薦】:更新數據庫時讓緩存失效,查詢時再更新緩存
- 如何保證緩存與數據庫的操作同時成功或同時失敗?
- 單體系統:將緩存與數據庫操作放在一個事務
- 分布式系統,利用TCC等分布式事務方案
- 先操作緩存還是先操作數據庫
- 先刪除緩存,再操作數據庫
- 先操作數據庫,再刪除緩存 【推薦】
三、如何選擇策略
- 數據一致性要求:強一致性選 Write-Through 或主動更新;弱一致性可選 Cache-Aside 或 TTL。
- 讀寫比例:讀多寫少優先 Cache-Aside;寫多考慮 Write-Behind。
- 系統復雜度:簡單場景用 TTL 或 Cache-Aside;復雜場景用事件驅動。
- 性能需求:高頻寫入可用 Write-Behind 降低延遲。
四、實際應用示例
- 電商商品詳情頁:使用 Cache-Aside + TTL,配合消息隊列主動更新熱點商品。
- 社交點贊數統計:采用 Write-Behind 異步更新數據庫,保證快速響應。
- 全局配置信息:Read-Through 確保每次讀取都是最新配置。
五、使用 Cache-Aside + TTL 的方式,實現緩存商鋪信息詳情
1.引入StringRedisTemplate
@Resourceprivate StringRedisTemplate stringRedisTemplate;
2.將查詢商鋪信息加入緩存
@Overridepublic Result queryById(Long id) {// 1.從redis查詢商鋪緩存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}// 4.不存在,根據id查詢數據庫Shop shop = getById(id);if (shop == null) {// 5.數據庫不存在,返回錯誤return Result.fail("店鋪不存在!");}// 6.存在,寫入redis,加入過期時間stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shop);}
3.更新商鋪信息時移除緩存
@Overridepublic Result update(Shop shop) {if (shop.getId() == null) {return Result.fail("店鋪id不能為空!");}// 1.更新數據庫updateById(shop);// 2.刪除緩存stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());return Result.ok();}
總結
使用 Cache-Aside + TTL 緩存更新策略的最佳實踐方案:
- 低一致性需求:使用Redis自帶的內存淘汰機制
- 高一致性需求:主動更新,并以超時剔除作為兜底方案
- 讀操作:
- 緩存命中則直接返回
- 緩存未命中則查詢數據庫,并寫入緩存,設定超時時間
- 寫操作:
- 先寫數據庫,然后再刪除緩存
- 要確保數據庫與緩存操作的原子性
- 讀操作:
六、注意事項
- 緩存穿透:惡意請求不存在的 key,可通過布隆過濾器防御。
- 緩存雪崩:大量 Key 同時失效,需設置隨機 TTL 或禁用失效時間。
- 緩存擊穿:熱點 Key 失效瞬間高并發,可用互斥鎖(Mutex Luck)保護。
緩存更新策略的選擇需結合業務場景、數據特點和技術架構綜合權衡,沒有絕對的最優解,只有最適合的方案。