redis和mysql數據一致性的問題
在這里,我們討論三種更新策略:
- 先更新緩存,再更新數據庫
- 先更新數據庫,再更新緩存
- 先刪除緩存,再更新數據庫
- 先更新數據庫,再刪除緩存
第一種,先更新緩存,再更新數據庫
問題:更新緩存成功,更新數據庫失敗,導致數據不一致。
第二種,先更新數據庫,再更新緩存
問題:
1、A更新數據庫
2、B更新數據庫
3、B寫入緩存
4、A寫入緩存
出現數據不一致。
考慮另一種情況, 有如下兩點:
(1)如果你是一個寫數據庫場景比較多,而讀數據場景比較少的業務需求,采用這種方案就會導致,數據壓根還沒讀到,緩存就被頻繁的更新,浪費性能。
(2)如果你寫入數據庫的值,並不是直接寫入緩存的,而是要經過一系列復雜的計算再寫入緩存。那么,每次寫入數據庫后,都再次計算寫入緩存的值,無疑是浪費性能的。顯然,刪除緩存更為適合。
第三種,先刪除緩存,再更新數據庫。
問題:
1、A刪除緩存
2、B查詢數據庫獲取舊值
3、B更新了緩存
4、A更新數據庫
出現數據不一致的問題
延時雙刪
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
redis.delKey(key);
}
問題一:延時雙刪,演變成了:先更新數據庫,再刪除緩存。。。。
比如:
1、A刪除緩存
2、B查詢數據庫獲取舊值
3、B更新了緩存
4、A更新數據庫
5、A延時刪緩存
1~3步執行后,數據庫和緩存是一致的,相當於沒刪除。
4~5步:先更新數據庫,再刪緩存。
所以延時雙刪演變成了:先更新數據庫,再刪除緩存。問題還是沒解決。。。
為什么?假設,此時,在第4步執行之前,又來了個查詢C,C查詢到舊值。第6步:C將舊值插入緩存。此時出現緩存和數據庫不一致。
延時並不能解決:C插入緩存的操作在第5步后面執行,比如C遇到網絡問題、GC問題等。當然這是小概率,但並不代表不存在。
當然,延時越長,這個問題越能規避。如果業務需求不是非常嚴格,是可以忽略的。
問題二:吞吐量
問題三:數據庫更新后,無法保證下一次查詢,從緩存獲取的值和數據庫是一致的。
第四種,先更新數據庫,再刪除緩存
問題:上面C的查詢,已經說明問題了。
出現數據不一致的概率,比較小。采取這個方案,取決於業務需求。
終極方案
請求串行化
真正靠譜的方案:將訪問操作串行化
- 先刪緩存,將更新數據庫的操作放進有序隊列中
- 從緩存查不到的查詢操作,都進入有序隊列
需要解決的問題:
- 讀請求積壓,大量超時,導致數據庫的壓力:限流、熔斷
- 如何避免大量請求積壓:將隊列水平拆分,提高並行度。
- 保證相同請求路由正確。