一般來說,只要你用到了緩存,不管是redis還是memcache,就可能會涉及到數(shù)據(jù)庫緩存與數(shù)據(jù)的一致性問題,這里我們以redis為例。
我們該如何保證Redis與數(shù)據(jù)庫的一致性呢?
?So easy:? ? ? ? ? ? ? ? ? (推薦學習:Redis視頻教程)
更新的時候,先更新數(shù)據(jù)庫,然后再刪除緩存。
讀的時候,先讀緩存;如果沒有的話,就讀數(shù)據(jù)庫,同時將數(shù)據(jù)放入緩存,并返回響應。
乍一看,一致性問題貌似很好的得到了解決。但仔細一想,你會發(fā)現(xiàn)還是有問題:如果先更新了數(shù)據(jù)庫,刪除緩存的時候失敗了怎么辦?那么數(shù)據(jù)庫中是新數(shù)據(jù),緩存中是老數(shù)據(jù),數(shù)據(jù)出現(xiàn)不一致了。
改進方案:
先刪除緩存,后更新數(shù)據(jù)庫。因為即使后面更新數(shù)據(jù)庫失敗了,緩存是空的,讀的時候會從數(shù)據(jù)庫中重新拉,雖然都是舊數(shù)據(jù),但數(shù)據(jù)是一致的。
所以方案就變成了:
更新的時候,先刪除緩存,然后再更新數(shù)據(jù)庫。
讀的時候,先讀緩存;如果沒有的話,就讀數(shù)據(jù)庫,同時將數(shù)據(jù)放入緩存,并返回響應。
到這里是不是問題就得到了徹底的解決了呢?
其實并沒有,在高并發(fā)的場景下,會出現(xiàn)這樣的情況:數(shù)據(jù)發(fā)生了變更,先刪除了緩存,然后去修改數(shù)據(jù)庫。此時還沒來得及修改,一個請求過來了,去讀緩存,發(fā)現(xiàn)緩存空了,去讀數(shù)據(jù)庫,讀到了準備修改前的舊數(shù)據(jù),并且把舊數(shù)據(jù)放到了緩存。
隨后,數(shù)據(jù)變更程序完成了數(shù)據(jù)庫的修改。那么完了,這個時候發(fā)生數(shù)據(jù)不一致了……
解決方案:
針對這種情況,可以先把“修改DB”的操作放到一個jvm隊列,后面讀請求過來之后,“更新緩存”的操作也放進同一個JVM隊列,每個隊列,對于一個作業(yè)線程,按照隊列的順序,依次執(zhí)行相關(guān)操作,這樣就可以保證“更新緩存”一定是在DB修改之后,以保證數(shù)據(jù)一致性,具體如下圖所示: