這篇文章為大家帶來了關於Redis如何解決快取不一致的問題,快取和資料庫的資料不一致是如何發生的,下面就一起來看一下吧,希望對大家有幫助。
推薦學習:Redis學習教學
首先,我們得清楚「資料的一致性」具體是啥意思。其實,這裡的「一致性」包含了兩種情況:
不符合這兩種情況的,就屬於快取和資料庫的資料不一致問題了。不過,當快取的讀寫模式不同時,快取資料不一致的發生情況不一樣,我們的應對方法也會有所不同,所以,我們先按照快取讀寫模式,來分別了解下不同模式下的快取不一致情況。我們可以把快取分成讀寫快取和只讀快取。
對於讀寫快取來說,如果要對資料進行增刪改,就需要在快取中進行,同時也要根據採取的寫回策略,決定是否同步寫回到資料庫中。
同步直寫策略:寫快取時,也同步寫資料庫,快取和資料庫中的資料一致;
非同步寫回策略:寫快取時不同步寫資料庫,等到資料從快取中淘汰時,再寫回資料庫。使用這種策略時,如果資料還沒有寫回資料庫,快取就發生了故障,那麼,此時,資料庫就沒有最新的資料了。
所以,對於讀寫快取來說,要確保快取和資料庫中的資料一致,就要採用同步直寫策略。不過,要注意的是,如果採用這種策略,就需要同時更新快取和資料庫。所以,我們要在業務應用中使用事務機制,來確保快取和資料庫的更新具有原子性,也就是說,兩者要不一起更新,要不都不更新,回傳錯誤訊息,進行重試。否則,我們就無法實現同步直寫。
當然,在有些場景下,我們對資料一致性的要求可能不是那麼高,比如說快取的是電商商品的非關鍵屬性或短影片的創建或修改時間等,那麼,我們可以使用非同步寫回策略。
下面我們再來說只讀快取。對於唯讀快取來說,如果有資料新增,會直接寫入資料庫;而有資料刪改時,就需要標記只讀快取中的資料無效。這樣一來,應用後續再存取這些增刪改的資料時,因為快取中沒有對應的數據,就會發生快取缺失。此時,應用程式再從資料庫中把資料讀入緩存,這樣後續再存取資料時,就能夠直接從快取中讀取了。
接下來,以Tomcat 向MySQL 寫入和刪改資料為例,來給你解釋一下,資料的增刪改操作具體是如何進行的,如下圖所示:
從圖中可以看到,Tomcat 上執行的應用,無論是新增(Insert 操作)、修改(Update 操作)、或刪除(Delete 操作)資料X,都會直接在資料庫中增改刪。當然,如果應用程式執行的是修改或刪除操作,也會刪除快取的資料 X。
那麼,這個過程會不會出現資料不一致的情況呢?考慮到新增資料和刪改資料的情況不一樣,所以我們分開來看。
我們假設應用程式先刪除緩存,再更新資料庫,如果快取刪除成功,但是資料庫更新失敗,那麼,應用再存取資料時,快取中沒有數據,就會發生快取缺失。然後,應用再存取資料庫,但是資料庫中的值為舊值,應用程式就存取到舊值了。
我來舉例說明一下,可以先看看下面的圖片
應用程式要把資料 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 會發現快取缺失,就只能去資料庫讀取。這會帶來兩個問題:
等到執行緒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影片教學
#以上是redis如何解決快取不一致的問題?的詳細內容。更多資訊請關注PHP中文網其他相關文章!