redis如何解決快取不一致的問題?

2022-02-25 19:00:36
本篇文章給大家帶來了關於如何解決快取不一致的問題,快取和資料庫的資料不一致是如何發生的,下面就一起來看一下吧,希望對大家有幫助。

推薦學習:

快取和資料庫的資料不一致是如何發生的?

首先,我們得清楚「資料的一致性」具體是啥意思。其實,這裡的「一致性」包含了兩種情況:

  • 快取中有資料,那麼,快取的資料值需要和資料庫中的值相同;
  • 快取中本身沒有資料,那麼,資料庫中的值必須是最新值。

不符合這兩種情況的,就屬於快取和資料庫的資料不一致問題了。不過,當快取的讀寫模式不同時,快取資料不一致的發生情況不一樣,我們的應對方法也會有所不同,所以,我們先按照快取讀寫模式,來分別瞭解下不同模式下的快取不一致情況。我們可以把快取分成讀寫快取和唯讀快取。

對於讀寫快取來說,如果要對資料進行增刪改,就需要在快取中進行,同時還要根據採取的寫回策略,決定是否同步寫回到資料庫中。

同步直寫策略:寫快取時,也同步寫資料庫,快取和資料庫中的資料一致;

非同步寫回策略:寫快取時不同步寫資料庫,等到資料從快取中淘汰時,再寫回資料庫。使用這種策略時,如果資料還沒有寫回資料庫,快取就發生了故障,那麼,此時,資料庫就沒有最新的資料了。

所以,對於讀寫快取來說,要想保證快取和資料庫中的資料一致,就要採用同步直寫策略。不過,需要注意的是,如果採用這種策略,就需要同時更新快取和資料庫。所以,我們要在業務應用中使用事務機制,來保證快取和資料庫的更新具有原子性,也就是說,兩者要不一起更新,要不都不更新,返回錯誤資訊,進行重試。否則,我們就無法實現同步直寫。

當然,在有些場景下,我們對資料一致性的要求可能不是那麼高,比如說快取的是電商商品的非關鍵屬性或者短視訊的建立或修改時間等,那麼,我們可以使用非同步寫回策略。

下面我們再來說說唯讀快取。對於唯讀快取來說,如果有資料新增,會直接寫入資料庫;而有資料刪改時,就需要把唯讀快取中的資料標記為無效。這樣一來,應用後續再存取這些增刪改的資料時,因為快取中沒有相應的資料,就會發生快取缺失。此時,應用再從資料庫中把資料讀入快取,這樣後續再存取資料時,就能夠直接從快取中讀取了。

接下來,以 Tomcat 向 MySQL 中寫入和刪改資料為例,來給你解釋一下,資料的增刪改操作具體是如何進行的,如下圖所示:

從圖中可以看到,Tomcat 上執行的應用,無論是新增(Insert 操作)、修改(Update 操作)、還是刪除(Delete 操作)資料 X,都會直接在資料庫中增改刪。當然,如果應用執行的是修改或刪除操作,還會刪除快取的資料 X。

那麼,這個過程中會不會出現資料不一致的情況呢?考慮到新增資料和刪改資料的情況不一樣,所以我們分開來看。

  1. 新增資料
    如果是新增資料,資料會直接寫到資料庫中,不用對快取做任何操作,此時,快取中本身就沒有新增資料,而資料庫中是最新值,這種情況符合我們剛剛所說的一致性的第 2 種情況,所以,此時,快取和資料庫的資料是一致的。
  2. 刪改資料
    如果發生刪改操作,應用既要更新資料庫,也要在快取中刪除資料。這兩個操作如果無法保證原子性,也就是說,要不都完成,要不都沒完成,此時,就會出現資料不一致問題了。這個問題比較複雜,我們來分析一下。

我們假設應用先刪除快取,再更新資料庫,如果快取刪除成功,但是資料庫更新失敗,那麼,應用再存取資料時,快取中沒有資料,就會發生快取缺失。然後,應用再存取資料庫,但是資料庫中的值為舊值,應用就存取到舊值了。
我來舉個例子說明一下,可以先看看下面的圖片

在這裡插入圖片描述

應用要把資料 X 的值從 10 更新為 3,先在 Redis 快取中刪除了 X 的快取值,但是更新資料庫卻失敗了。如果此時有其他並行的請求存取 X,會發現 Redis 中快取缺失,緊接著,請求就會存取資料庫,讀到的卻是舊值 10。
你可能會問,如果我們先更新資料庫,再刪除快取中的值,是不是就可以解決這個問題呢?我們再來分析下。
如果應用先完成了資料庫的更新,但是,在刪除快取時失敗了,那麼,資料庫中的值是新值,而快取中的是舊值,這肯定是不一致的。這個時候,如果有其他的並行請求來存取資料,按照正常的快取存取流程,就會先在快取中查詢,但此時,就會讀到舊值了。
我還是藉助一個例子來說明一下。

在這裡插入圖片描述

應用要把資料 X 的值從 10 更新為 3,先成功更新了資料庫,然後在 Redis 快取中刪除 X 的快取,但是這個操作卻失敗了,這個時候,資料庫中 X 的新值為 3,Redis 中的 X 的快取值為 10,這肯定是不一致的。如果剛好此時有其他使用者端也傳送請求存取 X,會先在 Redis 中查詢,該使用者端會發現快取命中,但是讀到的卻是舊值 10。

好了,到這裡,我們可以看到,在更新資料庫和刪除快取值的過程中,無論這兩個操作的執行順序誰先誰後,只要有一個操作失敗了,就會導致使用者端讀取到舊值。我畫了下面這張表,總結了剛剛所說的這兩種情況。

在這裡插入圖片描述

問題發生的原因我們知道了,那該怎麼解決呢?

如何解決資料不一致問題?

首先,我給你介紹一種方法:重試機制。

具體來說,可以把要刪除的快取值或者是要更新的資料庫值暫存到訊息佇列中(例如使用 Kafka 訊息佇列)。當應用沒有能夠成功地刪除快取值或者是更新資料庫值時,可以從訊息佇列中重新讀取這些值,然後再次進行刪除或更新。

如果能夠成功地刪除或更新,我們就要把這些值從訊息佇列中去除,以免重複操作,此時,我們也可以保證資料庫和快取的資料一致了。否則的話,我們還需要再次進行重試。如果重試超過的一定次數,還是沒有成功,我們就需要向業務層傳送報錯資訊了。

下圖顯示了先更新資料庫,再刪除快取值時,如果快取刪除失敗,再次重試後刪除成功的情況,你可以看下。

在這裡插入圖片描述

剛剛說的是在更新資料庫和刪除快取值的過程中,其中一個操作失敗的情況,實際上,即使這兩個操作第一次執行時都沒有失敗,當有大量並行請求時,應用還是有可能讀到不一致的資料。

同樣,我們按照不同的刪除和更新順序,分成兩種情況來看。在這兩種情況下,我們的解決方法也有所不同。

情況一:先刪除快取,再更新資料庫。

假設執行緒 A 刪除快取值後,還沒有來得及更新資料庫(比如說有網路延遲),執行緒 B 就開始讀取資料了,那麼這個時候,執行緒 B 會發現快取缺失,就只能去資料庫讀取。這會帶來兩個問題:

  1. 執行緒 B 讀取到了舊值;
  2. 執行緒 B 是在快取缺失的情況下讀取的資料庫,所以,它還會把舊值寫入快取,這可能會導致其他執行緒從快取中讀到舊值。

等到執行緒 B 從資料庫讀取完資料、更新了快取後,執行緒 A 才開始更新資料庫,此時,快取中的資料是舊值,而資料庫中的是最新值,兩者就不一致了。

我用一張表來彙總下這種情況。
在這裡插入圖片描述

這該怎麼辦呢?我來給你提供一種解決方案。

線上程 A 更新完資料庫值以後,我們可以讓它先 sleep 一小段時間,再進行一次快取刪除操作。

之所以要加上 sleep 的這段時間,就是為了讓執行緒 B 能夠先從資料庫讀取資料,再把缺失的資料寫入快取,然後,執行緒 A 再進行刪除。所以,執行緒 A sleep 的時間,就需要大於執行緒 B 讀取資料再寫入快取的時間。這個時間怎麼確定呢?建議你在業務程式執行的時候,統計下執行緒讀資料和寫快取的操作時間,以此為基礎來進行估算。

這樣一來,其它執行緒讀取資料時,會發現快取缺失,所以會從資料庫中讀取最新值。因為這個方案會在第一次刪除快取值後,延遲一段時間再次進行刪除,所以我們也把它叫做「延遲雙刪」。

下面的這段虛擬碼就是「延遲雙刪」方案的範例,你可以看下。

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

情況二:先更新資料庫值,再刪除快取值。

如果執行緒 A 刪除了資料庫中的值,但還沒來得及刪除快取值,執行緒 B 就開始讀取資料了,那麼此時,執行緒 B 查詢快取時,發現快取命中,就會直接從快取中讀取舊值。不過,在這種情況下,如果其他執行緒並行讀快取的請求不多,那麼,就不會有很多請求讀取到舊值。而且,執行緒 A 一般也會很快刪除快取值,這樣一來,其他執行緒再次讀取時,就會發生快取缺失,進而從資料庫中讀取最新值。所以,這種情況對業務的影響較小。

我再畫一張表,帶你總結下先更新資料庫、再刪除快取值的情況。
在這裡插入圖片描述

好了,到這裡,我們瞭解到了,快取和資料庫的資料不一致一般是由兩個原因導致的,我給你提供了相應的解決方案。

  • 刪除快取值或更新資料庫失敗而導致資料不一致,你可以使用重試機制確保刪除或更新操作成功。
  • 在刪除快取值、更新資料庫的這兩步操作中,有其他執行緒的並行讀操作,導致其他執行緒讀取到舊值,應對方案是延遲雙刪。

推薦學習:

以上就是redis如何解決快取不一致的問題?的詳細內容,更多請關注TW511.COM其它相關文章!