PS:本文已收錄到1.1K Star數開源學習指南——《大廠面試指北》,若是想要了解更多大廠面試相關的內容及獲取《大廠面試指北》離線PDF版,請掃描下方二維碼碼關注公衆號「大廠面試」,謝謝你們了!項目地址:github.com/NotFound9/i…html
《大廠面試指北》項目截圖:git
獲取《大廠面試指北》離線PDF版,請掃描下方二維碼關注公衆號「大廠面試」github
咱們平常開發中,對於緩存用的最多的場景就像下圖同樣,可能僅僅是對數據進行緩存,減輕數據庫壓力,縮短接口響應時間。 面試
高併發讀寫時,請求執行各步驟的順序是不可控的。假設此時有一個請求A,B都在在執行寫流程,請求A是須要將某個數據改爲1,請求B是須要將某個數據改成2,執行操做以下時就會致使數據不一致的問題:數據庫
1.請求A執行操做1.1刪除緩存。緩存
2.請求A執行操做1.2更新數據庫,將值改成1。安全
3.請求B執行操做1.1刪除緩存。服務器
4.請求B執行操做1.2更新數據庫,將值改成2網絡
5.假設說請求B所在服務器網絡延遲比較低,請求B先更新緩存,此時緩存中的key對應的value是2。併發
6.請求A更新緩存,將緩存中B更新的數據進行覆蓋,將key對應的值改成1。
此時數據庫中是B修改後的數據,值爲2,而緩存中的數據是1,這樣在緩存過時錢,用戶讀到的都是髒數據,與數據庫不一致。
高併發讀寫時,請求執行各步驟的順序是不可控的。假設此時有一個請求A在執行寫流程,將原值由1改爲2,請求B執行讀流程,執行操做以下時就會致使數據不一致的問題:
1.寫請求A執行1.1操做刪除緩存key,value是原值1。
2.讀請求B執行2.1操做發現緩存中沒有數據,就去執行2.2操做讀數據庫,讀到舊數據,值爲1。
3.寫請求A執行1.2操做更新數據庫,將數據由1改成2。
4.寫請求A執行1.3操做更新緩存,此時緩存中的數據key對應的value是2。
5.讀請求B執行2.3操做更新緩存,將以前讀到的舊數據1設置到緩存中,此時緩存中的數據key對應的value是1。
因此若是說讀請求B所在服務器網絡延遲比較高,去執行2.3操做比寫請求A晚,就會致使寫請求A更新完緩存後,讀請求B使用以前讀到的舊數據去更新緩存,此時緩存中數據就與數據庫中的不一致。
保證數據一致性,網上有不少種方案,例如:
1.先刪除緩存,再更新數據庫。
2.先更新數據庫,再刪除緩存。
3.先刪除緩存,再更新數據庫,而後異步延遲一段時間再去刪一次緩存。
可是這些方案都是存在各類各樣的問題,這裏篇幅有限,只給出目前相對正確的三套方案,目前的這些方案也有本身的侷限性。
1.寫請求更新以前先獲取分佈式鎖,得到以後才能去數據庫更新這個數據,獲取不到就進行等待,超時後就返回更新失敗。
2.更新完以後去刷新緩存,若是刷新失敗,放到內存隊列中進行重試(重試時取數據庫最新數據更新緩存)。
讀請求發現緩存中沒有數據時,直接去讀取數據庫,讀完更新緩存。
這種技術方案經過對寫請求的實現串行化來保證數據一致性,可是會致使吞吐量變低。比較適合銀行相關的業務,由於對於銀行項目來講,保證數據一致性比可用性更加劇要,就像是去存款機存錢,取錢時,爲了保證帳戶安全,都是會讓用戶執行操做後,等待一段時間才能得到反饋,這段時間其實取款機是不可用的。
1.先更新數據庫
2.異步刪除緩存(若是數據庫是讀寫分離的,那麼刪除緩存時須要延遲刪除,不然可能會在刪除緩存時,從庫尚未收到更新後的數據,其餘讀請求就去從庫讀到舊數據而後設置到緩存中。)
3.刪除緩存失敗時,將刪除的key放到內存隊列或者是消息隊列中進行異步重試
在更新完數據庫後,咱們爲何不直接更新,而是採用刪除緩存呢?
這是由於直接更新緩存的話,在高併發場景下,有多個更新請求時,難以保證後更新數據庫的請求會後更新緩存,也就是上面的高併發寫問題。若是採用刪除緩存,可讓下次讀時讀取數據庫,更新緩存,保證一致性。
2.cannal項目會讀取數據庫的binlog,而後解析後發消息到kafka。
3.而後緩存更新項目訂閱topic,從kafka接收到更新數據庫操做的消息後,更新緩存,更新緩存失敗時,新建異步線程去重試或者將操做發到消息隊列,後續再進行處理。
可是這種方案在更新數據庫後,緩存中仍是舊值,必須等緩存更新項目消費消息後,更新緩存,緩存中才是最新值。因此更新操做完成與更新生效之間會有必定的延遲。
你們有了解其餘的技術方案,歡迎進羣一塊兒討論!
參考連接: