如何保障mysql和redis之間的資料一致性?
一、如何保障mysql和redis之間的資料一致性?
在高併發的業務場景下,資料庫大多數情況都是使用者併發訪問最薄弱的環節。所以,就需要使用redis做一個緩衝操作,讓請求先訪問到redis,而不是直接訪問Mysql等資料庫。這樣可以大大緩解資料庫的壓力。Redis快取資料的載入可以分為懶載入和主動載入兩種模式,下面分別介紹在這兩種模式下的資料一致性如何處理。
二、懶載入
讀取快取步驟一般沒有什麼問題,但是一旦涉及到資料更新:資料庫和快取更新,就容易出現快取和資料庫間的資料一致性問題。不管是先寫資料庫,再刪除快取;還是先刪除快取,再寫庫,都有可能出現數據不一致的情況。舉個例子:
-
如果刪除了快取
- 如果先寫了庫,在刪除快取前,寫庫的執行緒宕機了,沒有刪除掉快取,則也會出現資料不一致情況。
因為寫和讀是併發的,沒法保證順序,就會出現快取和資料庫的資料不一致的問題。如何解決?
所以結合前面例子的兩種刪除情況,我們就考慮前後雙刪加懶載入模式。那麼什麼是懶載入?就是當業務讀取資料的時候再從儲存層載入的模式,而不是更新後主動重新整理,它涉及的業務流程如下如所示:
理解了懶載入機制後,結合上面的業務流程圖,我們講解下前後雙刪如何做?
(一)延遲雙刪
在寫庫前後都進行
方案一(一種思路,不嚴謹)
具體步驟是:
1)先刪除快取;
2)再寫資料庫;
3)休眠500毫秒(根據具體的業務時間來定);
4)再次刪除快取。
那麼,這個500毫秒怎麼確定的,具體該休眠多久呢?
需要評估自己的專案的讀資料業務邏輯的耗時。這麼做的目的,就是確保讀請求結束,寫請求可以刪除讀請求造成的快取髒資料。
當然,這種策略還要考慮 redis 和資料庫主從同步的耗時。最後的寫資料的休眠時間:則在讀資料業務邏輯的耗時的基礎上,加上幾百ms即可。比如:休眠1秒。
方案二,非同步延遲刪除:
1)先刪除快取;
2)再寫資料庫;
3)觸發非同步寫人序列化
4)mq接受再次刪除快取。
非同步刪除對線上業務無影響,序列化處理保障併發情況下正確刪除。
(二)為什麼要雙刪?
db更新分為兩個階段,更新前及更新後,更新前的刪除很容易理解,在db更新的過程中由於讀取的操作存在併發可能,會出現快取重新寫入資料,這時就需要更新後的刪除。
(三)雙刪失敗如何處理?
1、設定快取過期時間
從理論上來說,給快取設定過期時間,是保證最終一致性的解決方案。所有的寫操作以資料庫為準,只要到達快取過期時間,則後面的讀請求自然會從資料庫中讀取新值然後回填快取。
結合雙刪策略+快取超時設定,這樣最差的情況就是在超時時間內資料存在不一致。
2、重試方案
重試方案有兩種實現,一種在業務層做,另外一種實現中介軟體負責處理。
業務層實現重試如下:
(1)更新資料庫資料;
(2)快取因為種種問題刪除失敗;
(3)將需要刪除的key傳送至訊息佇列;
(4)自己消費訊息,獲得需要刪除的key;
(5)繼續重試刪除操作,直到成功。
然而,該方案有一個缺點,對業務線程式碼造成大量的侵入。於是有了方案二,在方案二中,啟動一個訂閱程式去訂閱資料庫的binlog,獲得需要操作的資料。在應用程式中,另起一段程式,獲得這個訂閱程式傳來的資訊,進行刪除快取操作。
中介軟體實現重試如下:
流程說明:
(1)更新資料庫資料;
(2)資料庫會將操作資訊寫入binlog日誌當中;
(3)訂閱程式提取出所需要的資料以及key;
(4)另起一段非業務程式碼,獲得該資訊;
(5)嘗試刪除快取操作,發現刪除失敗;
(6)將這些資訊傳送至訊息佇列;
(7)重新從訊息佇列中獲得該資料,重試操作。
三、主動載入
主動載入模式就是在db更新的時候同步或者非同步進行快取更新,常見的模式如下:
寫流程:
第一步先刪除快取,刪除之後再更新DB,之後再非同步將資料刷回快取。
讀流程:
第一步先讀快取,如果快取沒讀到,則去讀DB,之後再非同步將資料刷回快取。
這種模式簡單易用,但是它有一個致命的缺點就是併發會出現髒資料。
試想一下,同時有多個伺服器的多個執行緒進行’步驟1.2更新DB’,更新DB完成之後,它們就要進行非同步刷快取,我們都知道多伺服器的非同步操作,是無法保證順序的,所以後面的重新整理操作存在相互覆蓋的併發問題,也就是說,存在先更新的DB操作,反而很晚才去重新整理快取,那這個時候,資料也是錯的。
讀寫併發:再試想一下,伺服器A在進行’讀操作’,在A伺服器剛完成2.2時,伺服器B在進行’寫操作’,假設B伺服器1.3完成之後,伺服器A的2.3才被執行,這個時候就相當於更新前的老資料寫入快取,最終資料還是錯的。
而對於這種髒資料的產生歸其原因還是在於這種模式的主動重新整理快取屬於非冪等操作,那麼要解決這個問題怎麼辦?
- 前面介紹的雙刪操作方案,因為刪除每次操作都是無狀態的,所以是冪等的。
- 將重新整理操作序列處理。
這裡把基於序列處理的重新整理操作方案介紹一下:
寫流程:
第一步先刪除快取,刪除之後再更新DB,我們監聽從庫(資源少的話主庫也ok)的binlog,通過分析binlog我們解析出需要需要重新整理的資料標識,然後將資料標識寫入MQ,接下來就消費MQ,解析MQ訊息來讀庫獲取相應的資料重新整理快取。
關於MQ序列化,大家可以去了解一下 Kafka partition 機制 ,這裡就不詳述了。
讀流程:
第一步先讀快取,如果快取沒讀到,則去讀DB,之後再非同步將資料標識寫入MQ(這裡MQ與寫流程的MQ是同一個),接下來就消費MQ,解析MQ訊息來讀庫獲取相應的資料重新整理快取。
四、總結
- 懶載入模式快取可採取雙刪+TTL失效來實現;
- 雙刪失敗情況下可採取重試措施,重試有業務通過mq重試以及元件消費mysql的binlog再寫入mq重試兩種方式;
- 主動載入由於操作本身不具有冪等性,所以需要考慮載入的有序性問題,採取mq的分割槽機制實現序列化處理,實現快取和mysql資料的最終一致,此時讀和寫操作的快取載入事件是走的同一個mq。