資料庫與快取怎樣做同步最好
前言:
在讀取與寫入快取方面大家都是這麼做的:判斷是否有快取資料,無資料的話從資料庫載入,若查出資料不為null,則寫入快取,再把資料返回呼叫方。
但是這裡有一個問題需要分析,快取與資料庫的同步,在更新完資料庫後,是更新快取還是刪除快取,還是先刪快取,再更新資料庫。從理論上來說,設定過期時間是最終保持一致的解決方案。但是這不是最好的辦法,在快取有效期內或者高併發情況下,會很可能出現讀取到的資料與快取不一致的情況。
三種策略:
1.先更新資料庫,再更新快取
2.先刪除快取,再更新資料庫
3.先更新資料庫,再刪除快取
不會出現先更新快取再更新資料庫,如果更新資料庫失敗了,那就完了
1.先更新資料庫,再更新快取
這套方案不太好
a.執行緒安全形度
(1) 執行緒A更新了資料庫
(2) 執行緒B更新了資料庫
(3) 執行緒B更新了快取
(4) 執行緒A更新了快取
這樣快取中就出現了錯誤的資料庫
b.業務場景
如果資料庫寫的場景比較多,讀的場景比較少,就會出現資料庫頻繁更新,快取頻繁更新可能快取根本就沒有讀,這樣浪費效能
如果寫入資料庫的值,並不是直接寫入快取的,而是要經過一系列複雜的計算再寫入快取,那麼每次寫入資料庫後,再次計算寫入快取,也是浪費效能的。刪除快取顯得更為合適。
2.先刪快取,再更新資料庫
高併發操作下,依舊會出現問題
(1) A執行緒進行寫操作,先刪除快取
(2) B執行緒發現沒有快取,去資料庫讀取舊值
(3) B執行緒將舊值寫入快取
(4) A執行緒更新資料庫
這樣,資料庫中的資料,又是髒資料了,還有在資料庫主從分離情況下,中從沒來的及同步,結果把未同步的資料寫入到了快取。這怎麼解決
採用延時雙刪策略
redis.delKey(key);
db.update(Data);
new Thread(()->{Thread.sleep(1000);redis.delKey(key);}).start();
在不影響程式響應的情況下,開一個執行緒去刪除快取, 至於是多少時間後刪除,可以自己評估,不一定是1s。
但是這種情況下,如果第二次刪除快取失敗了怎麼辦?
3.先更新資料庫,再刪快取
這樣也有問題
(1) 快取剛好失效
(2) A查資料庫得到一箇舊值
(3) B將新值寫入資料庫
(4) B刪除快取
(5) A將舊值寫入快取
因為資料庫的讀比寫快,所以這種概率比較低正常順序應該是1 2 5 3 4,但是也是有可能發生的,也可能是查詢出來值後,經過一系列計算,又寫入快取的。解決辦法還是延時快取雙刪,但是和2一樣,刪除失敗了怎麼辦?
解決方案1
1.更新資料庫
2.刪除快取失敗
3.將要刪除的key傳送到訊息佇列
4.消費訊息刪除key,重試直到成功
解決方案2
(1)更新資料庫資料
(2)資料庫會將操作資訊寫入binlog日誌當中
(3)訂閱程式提取出所需要的資料以及key
(4)另起一段非業務程式碼,獲得該資訊
(5)嘗試刪除快取操作,發現刪除失敗
(6)將這些資訊傳送至訊息佇列
(7)重新從訊息佇列中獲得該資料,重試操作。
備註說明:上述的訂閱binlog程式在mysql中有現成的中介軟體叫canal,可以完成訂閱binlog日誌的功能。至於oracle中,博主目前不知道有沒有現成中介軟體可以使用。另外,重試機制,博主是採用的是訊息佇列的方式。如果對一致性要求不是很高,直接在程式中另起一個執行緒,每隔一段時間去重試即可,這些大家可以靈活自由發揮,只是提供一個思路。
參考文獻: