天天看點

Redis緩存與資料庫一緻性解決方案(上)1 什麼是緩存和DB的資料一緻性2 緩存的讀寫模式3 新增資料4 删改資料5 資料不一緻的解決方案

隻要使用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

Redis緩存與資料庫一緻性解決方案(上)1 什麼是緩存和DB的資料一緻性2 緩存的讀寫模式3 新增資料4 删改資料5 資料不一緻的解決方案

4.2 先更新DB,再删除Cache

Redis緩存與資料庫一緻性解決方案(上)1 什麼是緩存和DB的資料一緻性2 緩存的讀寫模式3 新增資料4 删改資料5 資料不一緻的解決方案

綜上,在更新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:

Redis緩存與資料庫一緻性解決方案(上)1 什麼是緩存和DB的資料一緻性2 緩存的讀寫模式3 新增資料4 删改資料5 資料不一緻的解決方案

此時,該怎麼辦呢?

解決方案

T1更新完DB後,讓它sleep一段時間,再删除緩存。

為什麼要sleep一段時間呢?

為了讓T2能夠先從DB讀資料,再把缺失資料寫入緩存,然後,T1再進行删除。

是以,T1 sleep的時間,就需要大于T2讀取資料再寫入緩存的時間。

這個時間怎麼确定?

在業務程式運作時,統計下線程讀資料和寫緩存的操作時間,以此為基礎來進行估算。

這樣,當其它線程讀資料時,會發現緩存缺失,是以會從DB讀最新值。因為這個方案會在第一次删除緩存值後,延遲一段時間再次進行删除,是以稱為“延遲雙删”。

redis.delKey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)