Redis與數據庫如何保持數據一致性


讀寫操作一致性分析

引言

首先,先說一下。老外提出了一個緩存一致性設計套路,名為《Cache-Aside pattern》。其中就指出

跟新:應用程序先從cache取數據,沒有得到,則從數據庫中取數據,成功后,放到緩存中。
命中:應用程序從cache中取數據,取到后返回。
失效:先把數據存到數據庫中,成功后,再讓緩存失效。
另外,知名社交網站facebook也在論文《Scaling Memcache at Facebook》中提出,他們用的是先更新數據庫,再刪緩存的策略

讀操作業務流程,大家應該沒啥疑問,操作流程如下:

寫操作流程分歧比較嚴重,如下分析三種更新緩存策略

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

第一種:先更新數據庫,再更新緩存分析

這種業界比較統一,從性能,業務,技術角度都不建議

  1. 線程安全角度
    同時有請求A和請求B進行更新操作,那么會出現
    (1)線程A更新了數據庫
    (2)線程B更新了數據庫
    (3)線程B更新了緩存
    (4)線程A更新了緩存
    這就出現請求A更新緩存應該比請求B更新緩存早才對,但是因為網絡等原因,B卻比A更早更新了緩存。這就導致了臟數據,因此不考慮。

  2. 業務場景角度
    有如下兩點:
    (1)如果你是一個寫數據庫場景比較多,而讀數據場景比較少的業務需求,采用這種方案就會導致,數據壓根還沒讀到,緩存就被頻繁的更新,浪費性能。
    (2)如果你寫入數據庫的值,並不是直接寫入緩存的,而是要經過一系列復雜的計算再寫入緩存。那么,每次寫入數據庫后,都再次計算寫入緩存的值,無疑是浪費性能的。顯然,刪除緩存更為適合。
    接下來討論的就是爭議最大的,先刪緩存,再更新數據庫。還是先更新數據庫,再刪緩存的問題。

第二種:先刪緩存,再更新數據庫

該方案會導致不一致的原因是。同時有一個請求A進行更新操作,另一個請求B進行查詢操作。那么會出現如下情形:
(1)請求A進行寫操作,刪除緩存
(2)請求B查詢發現緩存不存在
(3)請求B去數據庫查詢得到舊值
(4)請求B將舊值寫入緩存
(5)請求A將新值寫入數據庫

  • 上述情況就會導致不一致的情形出現。而且,如果不采用給緩存設置過期時間策略,該數據永遠都是臟數據。
    如何解決呢?采用延時雙刪策略,偽代碼如下:
 redis.deleteKey(key);
 userService.update(id);
       
 Thread.sleep(1000);
 redis.deleteKey(key);
  • 雙刪策略,休眠時間是考慮的重點,是休眠1s還是多久? 需要根據業務情況您的寫請求耗時多長,然后再此基礎上加上幾百ms即可
  • 假如刪除緩存失敗,如何處理?兩種方案處理,主要思想通過重試的機制刪除,直到成功為止
  1. 第一種方案:將刪除失敗的key放入消息隊列,再業務系統訂閱再重試機制刪除

  2. 第二種方案:將刪除失敗的key放入消息隊列,處理機制是將刪除失敗的key不再由業務系統處理,單獨啟獨立的線程及不影響業務系統的操作來做重試刪除機制

第三種:先更新數據庫,再刪除緩存

這種情況極端情況會存在並發問題么,假設這會有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那么會有如下情形產生
(1)緩存剛好失效
(2)請求A查詢數據庫,得一個舊值
(3)請求B將新值寫入數據庫
(4)請求B刪除緩存
(5)請求A將查到的舊值寫入緩存

分析發生這種情況的概率又有多少呢?發生上述情況有一個先天性條件,就是步驟(3)的寫數據庫操作比步驟(2)的讀數據庫操作耗時更短,才有可能使得步驟(4)先於步驟(5)可是,大家想想,數據庫的讀操作的速度遠快於寫操作的(不然做讀寫分離干嘛,做讀寫分離的意義就是因為讀操作比較快,耗資源少),因此步驟(3)耗時比步驟(2)更短;這種情況發生概覽極其低的, 正如引言所言fackbook采用的是這種方案;

小結: 沒有一種方案策略是完美的,一致性問題是分布式存儲解決方案一直以來的痛點, 問題都需要根據具體的業務場景再具體的分析,如上方案僅供參考;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM