首頁 >資料庫 >Redis >如何保證Redis快取與資料庫的一致性

如何保證Redis快取與資料庫的一致性

WBOY
WBOY轉載
2022-03-17 18:50:153178瀏覽

這篇文章為大家帶來了關於Redis的相關知識,其中主要介紹如何保證redis快取與資料庫的一致性相關問題,其中包括了更新快取和更新資料庫等等,希望對大家有幫助。

如何保證Redis快取與資料庫的一致性

推薦學習:Redis學習教學

#1、四個同步策略:

想要確保緩存與資料庫的雙寫一致,一共有4種方式,即4種同步策略:

  1. 先更新緩存,再更新資料庫;
  2. 先更新資料庫,再更新快取;
  3. 先刪除快取,再更新資料庫;
  4. 先更新資料庫,再刪除快取。

從這4種同步策略中,我們需要做出比較的是:

更新快取與刪除快取哪種方式比較適合?應該先操作資料庫還是先操作快取?

2、更新快取還是刪除快取

下面,我們來分析一下,應該採用更新快取還是刪除快取的方式。

2.1 更新快取

優點每次資料變更都會及時更新緩存,所以查詢時不容易出現未命中的情況。

缺點更新快取的消耗比較大。如果資料需要經過複雜的計算再寫入緩存,那麼頻繁的更新緩存,就會影響伺服器的效能。如果是寫入資料頻繁的業務場景,那麼可能頻繁的更新快取時,卻沒有業務讀取該資料。

2.2 刪除快取

優點操作簡單,無論更新操作是否複雜,都是直接刪除快取中的資料。

缺點刪除快取後,下次查詢快取會出現未命中,這時需要重新讀取一次資料庫。從上面的比較來看,一般情況下,刪除快取是更優的方案。

3、先操作資料庫還是快取

下面,我們再來分析一下,應該先操作資料庫還是先操作快取。
首先,我們將先刪除快取與先更新資料庫,在出現失敗時做一個比較:

3.1 先刪除快取再更新資料庫

如何保證Redis快取與資料庫的一致性
如上圖,是先刪除快取再更新資料庫,出現失敗時可能出現的問題:

  • 執行緒A刪除快取成功,執行緒A更新資料庫失敗;
  • 線程B從快取中讀取資料;由於快取被刪,進程B無法從快取中得到數據,進而從資料庫讀取資料;此時資料庫中的資料更新失敗,線程B從資料庫成功取得舊的數據,然後將資料更新到了快取。
  • 最終,快取和資料庫的資料是一致的,但仍然是舊的資料

3.2 先更新資料庫再刪除快取

如何保證Redis快取與資料庫的一致性
如上圖,是先更新資料庫再刪除緩存,在出現失敗時可能出現的問題:

  • 執行緒A更新資料庫成功,執行緒A刪除快取失敗;
  • 執行緒B讀取快取成功,由於快取刪除失敗,所以執行緒B讀取到的是快取中舊的資料。
  • 最後線程A刪除快取成功,有別的線程訪問緩存同樣的數據,與資料庫中的數據是一樣。
  • 最終,快取和資料庫的資料是一致的,但是會有一些執行緒讀到舊的資料。

經過上面的比較,我們發現在出現失敗的時候,是無法明確分辨出先刪快取和先更新資料庫哪個方式更好,以為它們都存在問題。後面我們會進一步對這兩種方式進行比較,但是在這裡我們先探討一下,上述場景出現的問題,該如何解決呢?

實際上,無論上面我們採用哪種方式去同步快取與資料庫,在第二步出現失敗的時候,都建議採用重試機制解決,上面兩張圖中已經畫了。




下面我們再將先刪快取與先更新資料庫,在沒有出現失敗時進行比較:
如何保證Redis快取與資料庫的一致性
#如上圖,是先刪除快取再更新資料庫,在沒有出現失敗時可能出現的問題:

  • 線程A刪除快取成功;
  • 線程B讀取快取失敗;
  • 線程B讀取資料庫成功,得到舊的資料;
  • 線程B將舊的資料成功地更新到了快取;
  • 線程A將新的資料成功地更新到資料庫。

可見,進程A的兩步驟操作都成功,但由於存在並發,在這兩個步驟之間,進程B存取了快取。 最終結果是,快取中儲存了舊的數據,而資料庫中儲存了新的數據,二者數據不一致。



如何保證Redis快取與資料庫的一致性
如上圖,是先更新資料庫再刪除緩存,在沒有出現失敗時可能出現的問題:

  • 線程A更新資料庫成功;
  • 線程B讀取快取成功;
  • 線程A刪除快取成功。

可見,最終快取與資料庫的資料是一致的,而且都是最新的資料。但線程B在這個過程裡讀到了舊的數據,可能還有其他線程也像線程B一樣,在這兩步之間讀到了緩存中舊的數據,但因為這兩步的執行速度會比較快,所以影響不大。對於這兩步驟之後,其他行程再讀取快取資料的時候,就不會出現類似進程B的問題了。

最終結論:

經過比較你會發現,先更新資料庫、再刪除快取是影響更小的方案。如果第二步出現失敗的情況,則可以採用重試機制解決問題。

4、延時雙刪

上面我們提到,如果是先刪快取、再更新資料庫,在沒有出現失敗時可能會導致數據的不一致。如果在實際的應用中,出於某些考慮我們需要選擇這種方式,那麼有辦法解決這個問題嗎?答案是有的,就是採用延時雙刪的策略,延時雙刪的基本想法如下

    ##刪除快取;
  1. 更新資料庫;
  2. sleep N毫秒;
  3. 再次刪除快取。
  4. 	public void write(String key, Object data) {
            Redis.delKey(key);
            db.updateData(data);
            Thread.sleep(1000);
            Redis.delKey(key);
        }

阻塞一段時間之後,再刪除緩存,就可以把這個過程中快取中不一致的資料刪除掉。而具體的時間,要評估你這項業務的大致時間,按照這個時間來設定即可。

4.1 採用讀寫分離的架構怎麼辦?

如果資料庫採用的是讀寫分離的架構,那麼又會出現新的問題,如下圖:


如何保證Redis快取與資料庫的一致性 此時來了兩個請求,請求A(更新操作) 和請求B(查詢操作)

    請求A 更新操作,刪除了Redis;
  1. 請求主庫進⾏更新操作,主庫與從庫進行同步資料的操作;
  2. 請B 查詢操作,發現Redis 中沒有數據;
  3. 去從庫中拿去數據;
  4. 此時同步數據還未完成,拿到的數據是舊資料;
此時的解決方法就是如果是對Redis 進行填充資料的查詢資料庫操作,那麼就強制將其指向主庫進⾏查詢。

刪除失敗了怎麼辦?

如果刪除依然失敗,則可以增加重試的次數,但是這個次數要有限制,當超出一定的次數時,要採取報錯、記日誌、發郵件提醒等措施。

5、利用訊息佇列進行刪除的補償

先更新資料庫,後刪除快取這⼀種情況也會出現問題,例如更新資料庫成功了,但是在刪除快取的階段出錯了沒有刪除成功,那麼此時再讀取快取的時候每次都是錯誤的資料了。
如何保證Redis快取與資料庫的一致性

此時解決方案就是利用訊息佇列進行刪除的補償。具體的業務邏輯⽤語⾔描述如下:

    請求線程A 先對資料庫進行更新操作;
  1. 在對Redis 進行刪除操作的時候發現報錯,刪除失敗;
  2. 此時將Redis 的key 作為訊息體傳送到訊息佇列中;
  3. 系統接收到訊息佇列發送的訊息後再次對Redis 進行刪除動作;
但這個方案會有⼀個缺點就是會對業務代碼造成大量的侵入,深深的耦合在⼀起,所以這時會有⼀個優化的方法,我們知道對Mysql 資料庫更新操作後再binlog 日誌中我們都能夠找到對應的操作,那麼我們可以訂閱Mysql 資料庫的binlog 日誌對快取進行操作。


如何保證Redis快取與資料庫的一致性

推薦學習:

Redis教學

以上是如何保證Redis快取與資料庫的一致性的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除