隻要使用Redis做緩存,就必然存在緩存和DB資料一緻性問題。若資料不一緻,則業務應用從緩存讀取的資料就不是最新資料,可能導緻嚴重錯誤。比如将商品的庫存緩存在Redis,若庫存數量不對,則下單時就可能出錯,這是不能接受的。
1 什麼是緩存和DB的資料一緻性
一緻性
包含如下情況:
-
緩存有資料
緩存的資料值需和DB相同
-
緩存無資料
DB必須是最新值
不符合這兩種情況的,都屬于緩存和DB資料不一緻。
2 緩存的讀寫模式
根據是否接收寫請求,可将緩存分成讀寫緩存和隻讀緩存。
2.1 讀寫緩存
若要對資料進行增删改,需要在Cache進行。
同時根據采取的寫回政策,決定是否同步寫回DB:
2.1.1 同步直寫
寫緩存時,也同步寫資料庫,緩存和資料庫中的資料一緻。
2.1.2 異步寫回
寫緩存時不同步寫DB,等到資料從緩存中淘汰時,再寫回DB。使用這種政策時,若資料還沒有寫回DB,緩存就發生故障,則此時,DB就沒有最新資料了。
是以,對于讀寫緩存,要想保證緩存和DB資料一緻,就要采用同步直寫。若采用這種政策,就需同時更新緩存和DB。是以,要在業務代碼中使用事務,保證緩存和DB更新的原子性,即兩者:
要麼一起更新
要麼都不更新,傳回錯誤資訊,進行重試
否則,我們無法實作同步直寫。
有些場景下,我們對資料一緻性要求不高,比如緩存的是電商商品的非關鍵屬性或短視訊的建立或修改時間等,則可以使用異步寫回。
2.2 隻讀緩存
-
新增資料
直接寫DB
-
删改資料
删改DB,删除隻讀緩存中的資料
這樣應用後續再通路這些增删改的資料時,由于Cache無資料 =》緩存缺失。
此時,再從DB把資料讀入Cache,這樣後續再通路資料時,直接讀Cache。
下面我們針對隻讀緩存,看看具體會遇到哪些問題,又該如何解決。
3 新增資料
資料直接寫到DB,不操作Cache。此時,Cache本身無新增資料,而DB是最新值,是以,此時緩存和DB資料一緻。
4 删改資料
此時應用既要更新DB,也要删除Cache。這倆操作若無法保證原子性,就可能出現資料不一緻。
4.1 先删Cache,再更新DB

4.2 先更新DB,再删除Cache
綜上,在更新DB和删除Cache時,無論這倆操作誰先執行,隻要有一個操作失敗了,就會導緻用戶端讀到舊值。
那怎麼辦?好像怎麼都會導緻資料不一緻?
5 資料不一緻的解決方案
5.1 無并發
重試
将:
要删除的Cache值
或要更新的DB值
暫存到MQ。
當應用删除Cache或更新DB:
成功
把這些值從MQ去除,避免重複操作,這時即可保證DB、Cache資料一緻性。
失敗
重試。從MQ重新讀取這些值,然後再次進行删除或更新。若重試超過一定次數,還沒成功,就向業務層發送報錯資訊。
在更新資料庫和删除緩存值的過程中,其中一個操作失敗了:
先更新DB,再删除緩存
- 若删除緩存失敗,再次重試後删除成功
-
Redis緩存與資料庫一緻性解決方案(上)1 什麼是緩存和DB的資料一緻性2 緩存的讀寫模式3 新增資料4 删改資料5 資料不一緻的解決方案 - 其它情況不再贅述。
即使這兩個操作第一次執行時都沒有失敗,當有大量并發請求時,應用還是有可能讀到不一緻的資料。
按不同的删除和更新順序,分成兩種情況來看
5.2 高并發
5.2.1 先删除Cache,再更新DB
假設現有時刻t1< t2 < t3,線程 T1、T2:
此時,該怎麼辦呢?
解決方案
T1更新完DB後,讓它sleep一段時間,再删除緩存。
為什麼要sleep一段時間呢?
為了讓T2能夠先從DB讀資料,再把缺失資料寫入緩存,然後,T1再進行删除。
是以,T1 sleep的時間,就需要大于T2讀取資料再寫入緩存的時間。
這個時間怎麼确定?
在業務程式運作時,統計下線程讀資料和寫緩存的操作時間,以此為基礎來進行估算。
這樣,當其它線程讀資料時,會發現緩存缺失,是以會從DB讀最新值。因為這個方案會在第一次删除緩存值後,延遲一段時間再次進行删除,是以稱為“延遲雙删”。
redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)