14.6緩存異常
四個方面
- 緩存中數據和數據庫不一致
- 緩存雪崩
- 緩存擊穿
- 緩存穿透
14.6.1數據不一致:
一致性包括兩種情況
- 緩存中有數據,需要和數據庫值相同
- 緩存中沒有數據,數據庫中的數據是最新值
如果不符合以上兩種情況,則出現數據不一致的問題。
讀寫緩存
- 同步直寫
- 異步寫回
只讀緩存
- 新增數據
- 數據直接寫到數據庫中,緩存不做操作。滿足一致性兩種情況的第2種。
- 刪改數據
- 先刪除緩存,后更新數據庫。可能會導致,緩存刪除成功,數據庫更新失敗。業務邏輯去訪問數據時,緩存中查不到數據,緩存缺失,到數據庫中查詢,所以只拿到舊的數據。
- 如果新更新數據庫,再刪除緩存。可能會導致,數據庫更新成功,緩存刪除失敗。數據庫中的數據是新的值,緩存中存儲的是舊值。再讀取時,先從緩存中讀取,讀取到了舊值。
解決數據不一致的方案
- 重試機制:把刪除的緩存值或要更新數據庫值先存儲到消息隊列中(kafka消息隊列)。
- 如果發現試了10次還不成功就會向服務器端報錯
多線程訪問的情況
- 先刪除緩存,再更新數據庫。
- 假設T1線程先刪除緩存,再執行更新數據庫。還未更新成功時,T2線程進行讀取,發現緩存中沒有數據,到數據庫中讀取,會讀取到舊的數據。如果T2還將舊數據更新到緩存中,那T1線程再進行讀取,也讀到的舊值。
- 讓T1線程先執行休眠一段時間。T1線程在休眠時間,讓T2線程執行結束,會將數據重新寫入緩存。T1線程再做一次緩存刪除操作。“延遲雙刪”。
redis.delCache() db.update() Thread.sleep(2000) redis.delCache()
- 先更新數據庫值,再去刪除緩存
- 假設T1線程先刪除或更新數據庫中的值,還沒來得及刪除緩存時,T2線程就開始讀取數據。T2會先從緩存中讀取,緩存命中,T2拿到的就是舊的數據。直到T1將緩存中數據刪除,其他線程再次讀取,可以拿到新值.
并發操作 | 執行順序 | 可能出現問題 | 問題描述 | 解決方案 |
---|---|---|---|---|
沒有 | 先刪除緩存,后更新數據庫 | 緩存刪除成功,數據庫更新失敗 | 從數據庫讀到舊數據 | 重試(通過消息隊列) |
有 | 先刪除緩存,后更新數據庫 | 緩存刪除,未更新數據庫,其他線程并發訪問 | 并發線程從數據庫讀到舊值,并更新了緩存,其他線程都從緩存中讀到舊值 | 延遲雙刪 |
沒有 | 先更新數據庫,后刪除緩存 | 數據庫更新成功,緩存刪除失敗 | 從緩存中讀到舊值 | 重試(通過消息隊列) |
有 | 先更新數據庫,后刪除緩存 | 數據庫更新成功,未刪除緩存,其他同線程并發訪問 | 并發線程從緩存讀到舊值 | 會有數據不一致情況短暫存在 |
14.6.2 緩存雪崩
大量的應用請求無法在redis中完成處理。緩存中讀取不到數據,直接進入到數據庫服務器。數據庫壓力激增,數據庫崩潰,請求堆積在redis,導致redis服務器崩潰,導致redis集群崩潰,應用服務器崩潰,稱為雪崩
原因1:緩存中有大量數據同時過期
解決方案:
- 頁面靜態化處理數據:對于不經常更換的數據,生成靜態頁
- 避免大量數據同時過期:為商品過期時間追加一個隨機數,在一個較小的范圍內(1~3分鐘)。
- 構建多級緩存架構:redis緩存+nginx緩存+ehcache緩存
- 延長或取消熱度超高的數據過期時間
- 服務降級
不同的數據采取不同的處理方式。
原因2:redis實例故障
解決方案:
- 服務熔斷或限流處理
提前預防:
- 災難預警:監控redis服務器性能指標,包括數據庫服務器性能指標,CPU、內存、平均響應時間、線程數等
- 集群:有節點出一故障,主從切換。
14.6.2 緩存擊穿
對某個訪問頻繁熱點數據的請求。主要發生在熱點數據失效
解決方案:
- 預先設定:電商雙11,商鋪設定幾款是主打商品,延長過期時間
- 實時監控:監控訪問量,避免訪問量激增
- 定時任務:啟動任務調度器,后臺刷新數據有效期
- 分布式鎖:可防止緩存擊穿,但會有性能問題 (不推薦)
14.6.3 緩存穿透
要訪問的數據在redis中不存在,在數據庫中也不存在。
原因:
- 業務層誤操作
- 惡意攻擊
解決方案:
- 緩存空值或缺省值
- 使用布隆過濾器,快速判斷數據是否存在
- 在請求入口前端進行請求檢測
- 實時監控
- key加密