緩存一致性?get💡

大家好,我是老三,今天又是被算法致郁的一天,寫篇文章緩一緩。

這篇文章,我們來看看緩存一致性問題。

緩存一致性

我接下來會巴巴說一堆緩存一致性,但是——

作為一名暴躁老哥,我先把結論撂這了!

緩存和數據庫的強一致性無法實現!

CAP理論了解一下,緩存適用的場景屬於CAP中的AP,是非強一致性的場景。

那還扯個犢子的緩存一致性?洗洗睡吧。

BASE理論接着了解一下,強一致性保證不了,那隻好委屈求全,盡量保證最終一致性唄。

最終一致性強調的是系統中所有的數據副本,在經過一段時間的同步後,最終能夠達到一個一致的狀態。因此,最終一致性的本質是需要系統保證最終數據能夠達到一致,而不需要實時保證系統數據的強一致性。

所以,我們追求的是儘可能保證緩存和數據庫的最終一致性。

CAP和BASE理論

先更新數據庫,再刪除緩存

Cache Aside Pattern

在開始之前,我們先來科普一下緩存+數據庫讀寫,最經典的Cache Aside Pattern。

  • 讀取:先讀取緩存,緩存里沒有,讀取數據庫,然後返迴響應,順斌保存緩存

讀取

  • 更新:先更新數據庫,然後刪除緩存

更新

為什麼是刪除緩存,而不是更新緩存?

  • 並發情況下更新緩存可能會帶來種種問題,直接刪除緩存更加穩妥。
  • 緩存更新在很多時候需要耗費資源,直接刪除,用時再從數據庫讀取,寫進緩存,更省性能。

一致性問題

那麼我們採用這種先更新數據庫,再刪除緩存,可能會出現什麼問題呢?

假如,我們更新數據庫成功,接下來還沒來刪除緩存,或者刪除緩存失敗怎麼辦?

那麼很明顯,這時候其它線程進來讀的就是臟數據。

先更數據庫,後刪緩存問題

那怎麼解決呢?

解決方案

既然刪除緩存失敗會導致臟數據,那我們就想辦法讓它能刪除成功唄。

消息隊列重試機制

我們可以引入一個重試機制。

如果刪除緩存失敗,向消息隊列發送消息,把刪除失敗的key放進去,消費消息隊列,獲取要刪除的key,然後去重試刪除。

引入消息隊列重試

但是,這麼干,好好的業務,咱們又引入了消息隊列,對現有的業務造成了入侵,複雜度又提升了。

監聽binlog異步刪除

其實還有另外一種辦法,我們可以用一個服務(比如阿里的 canal)去監聽數據庫的binlog,獲取需要操作的數據。

然後用另外一個服務獲取訂閱程序傳來的信息,進行緩存刪除操作。

監聽binlog異步刪除

這樣一來,對我們本身的業務入侵就小了很多。

先刪除緩存,再更新數據庫

一致性問題

我們看一下,如果先刪除緩存,再更新數據庫可能會帶來什麼問題。

在並發情況下,先刪除緩存,再更新數據庫,此時數據庫還未更新成功,這時候有其它線程進來了,讀取緩存,緩存不存在,讀取數據庫,讀取的是舊值,這時候,緩存不一致就發生了。

先刪緩存,後更數據庫不一致

解決方案

延時雙刪

延時雙刪是什麼意思呢?

就是在刪除緩存,更新數據庫之後,休眠一段時間後,再次刪除緩存。

延時雙刪

延時刪除之後,就把緩存里緩存的舊值給刪除了。

再有請求進來,就是讀取數據庫里的新值,再把新值保存進緩存。

當然,第二次刪除也有失敗的可能,怎麼辦呢?重試。那怎麼重試呢?前面寫了。

關於刪除,還有一個兜底的方案——設置緩存過期時間,這樣一來,哪怕緩存了臟數據,但是臟數據總有過期的時候,不至於一直不一致。

總結

我們來簡單總結一下,首先對緩存的操作,刪除優於更新,所以要刪除,而不是更新。

刪除緩存兩種方式:

  • 先更新數據庫,在刪除緩存。緩存不一致的兩種處理方式是消息隊列重試機制binlog異步刪除
  • 先刪除緩存,再更新數據庫。緩存不一致的處理方式是延時雙刪

當然,這些方案無疑都增加了系統的複雜度。

如果不是並發特別高的話,就沒有必要過度設計。

簡單的事情重複做,重複的事情認真做,認真的事情有創造性地做。

我是三分惡,一個努力學習中的程序員。

點贊關注不迷路,咱們下期見!


參考:

[1]. 緩存與數據庫一致性問題深度剖析

[2]. 數據庫和緩存一致性的幾種實現方式,我們來聊聊?

[3]. 面試官:緩存一致性問題怎麼解決?

[4]. 美團二面:Redis與MySQL雙寫一致性如何保證?