這一節,我們來學習一下緩存異常。緩存異常有四種類型,分別是緩存和數據庫的數據不一致、緩存雪崩、緩存擊穿和緩存穿透。
下面通過了解這四種緩存異常的原理和應對方法。
緩存和數據庫的數據不一致
緩存和數據庫的數據一致性包含兩種情況:
- 緩存中有數據,緩存的數據值需要和數據庫中的值相同;
- 緩存中沒有數據,數據庫中的值必須是最新值。
數據不一致是如何發生的?
在第1講中關於緩存的類型那節,介紹了緩存有兩種不同類型,分別是只讀緩存和讀寫緩存。不同類型的緩存數據不一致的發生情況不一樣,應對方法也不一樣。
讀寫緩存:有兩種寫回策略,同步直寫和異步寫回。如果要保證數據一致,就要采用同步直寫策略。但需要保證緩存和數據庫的更新具有原子性,即要么都成功,要么都失敗。
只讀緩存:分新增數據和刪改數據兩種情況說明。
新增數據
數據直接寫到數據庫中,不對緩存做任何操作,符合一致性的第2種情況。
刪改數據
發生刪改操作時,既要更新數據庫,也要在緩存里刪除數據。因為緩存和數據庫是不同的系統,這里分兩種情況:
- 先刪除緩存,再更新數據庫:數據庫更新失敗,導致請求再次訪問緩存時,發現緩存失敗,再讀數據庫時,從數據庫中讀取舊值。
- 先更新數據庫,再刪除緩存:緩存刪除失敗,導致請求再次訪問緩存時,發現緩存命中,並從緩存中讀取到舊值。
如何解決數據不一致?
使用重試機制,指把刪除的緩存值或者是要更新的數據庫值暫存到消息隊列中(例如使用Kafka消息隊列)。
當應用沒有能夠成功地刪除緩存值或者是更新數據庫值時,從消息隊列中重新讀取這些值,然后再次進行刪除或更新。
如果成功刪除,就從消息隊列中刪除,以免重復操作。否則就要進行重試,如果重試超過一定次數,就要向業務層發送報錯信息。
具體情況如下圖所示:
總結一下,對於只讀緩存來說,建議優先使用先更新數據庫,再刪除緩存。
緩存雪崩
緩存雪崩,指大量的應用請求無法在Redis緩存中進行處理,然后應用將大量請求發送到數據庫層,導致數據庫層的壓力激增。
導致緩存雪崩的兩個原因:
1. 緩存中有大量數據同時過期,導致大量請求無法得到處理。
解決方案有兩個,一是避免給大量的數據設置相同的過期時間,增加一個較小的隨機數(例如,隨機增加1~3分鍾)。
另一個是服務降級,服務降級指發生緩存雪崩時,針對不同的數據采取不同的處理方式:
- 非核心數據,暫時停止從緩存中查詢,直接返回預定義信息、空值或者錯誤信息;
- 核心數據,允許查詢緩存,如果緩存缺失,繼續通過數據庫讀取。
2. Redis緩存實例發生故障宕機了,無法處理請求。
有兩個建議,一是在業務系統中實現服務熔斷或請求限流機制。
服務熔斷是指在發生緩存雪崩時,為了防止引發連鎖的數據庫雪崩,暫停業務應用對緩存系統的接口訪問。
具體點,就是業務應用調用緩存接口時,緩存客戶端並不把請求發給Redis緩存實例,而是直接返回,等Redis緩存實例重新恢復服務后,再允許發送。
服務熔斷會暫停了整個緩存系統的訪問,對業務應用的影響范圍大。而請求限流相比服務熔斷造成的影響沒那么大。
請求限流是指業務系統的請求入口前端控制每秒進入系統的請求數,避免過多的請求被發送到數據庫。
二是事前預防,通過主從節點構建Redis緩存高可靠集群。
緩存擊穿
緩存擊穿,指針對某個訪問非常頻繁的熱點數據的請求,無法在緩存中進行處理,大量請求發送到后端數據庫,導致數據庫壓力激增,影響數據庫處理其他請求。
解決方案是,對於訪問特別頻繁的熱點數據,不設置過期時間。
緩存穿透
緩存穿透,指要訪問的數據既不在Redis緩存中,也不在數據庫中,導致請求在訪問緩存時,發生緩存缺失,再去訪問數據庫時,也發現沒有數據。
如果有大量請求訪問數據,就會同時給緩存和數據庫帶來巨大壓力。
發生緩存穿透有兩種情況:
- 業務層誤操作:緩存中的數據和數據庫中的數據被誤刪除;
- 惡意攻擊:專門訪問數據庫中沒有的數據。
為了避免緩存穿透,有三種應對方案。
第一種方案是,緩存空值或缺省值
一旦發生緩存穿透,就針對查詢的數據,在Redis中緩存一個空值或是和業務層協商確定的缺省值。
第二種方案是,使用布隆過濾器快速判斷數據是否存在,避免從數據庫中查詢數據是否存在,減輕數據庫壓力
布隆過濾器由一個初值都為0的bit數組和N個哈希函數組成,可以用來快速判斷某個數據是否存在。
通過三個操作完成標記:
- 使用N個哈希函數,分別計算這個數據的哈希值,得到N個哈希值
- 把這N個哈希值對bit數組的長度取模,得到每個哈希值的位置
- 把對應的位置的bit位設置為1
這樣一來,即使發生緩存穿透,大量請求只會查詢Redis和布隆過濾器。
第三種方案是,在請求入口的前端進行請求檢測
例如對請求進行合法性檢測,把惡意的請求(例如請求參數不合理、請求參數是非法值、請求字段不存在)直接過濾掉
總結
另外還有三個建議:
- 針對緩存雪崩,合理地設置數據過期時間,以及搭建高可靠緩存集群。
- 針對緩存擊穿,在緩存訪問非常頻繁的熱點數據時,不要設置過期時間。
- 針對緩存穿透,提前在入口前端實現惡意請求檢測,或者規范數據庫的數據刪除操作,避免誤刪除。