Redis和資料庫 資料同步問題
Redis和資料庫同步問題
快取充當資料庫
比如說Session這種訪問非常頻繁的資料,就適合採用這種方案;當然了,既然沒有涉及到資料庫,那麼也就不會存在一致性問題;
快取充當資料庫熱點快取
讀操作
目前的讀操作有個固定的套路,如下:
-
客戶端請求伺服器的時候,發現如果伺服器的快取中存在,則直接取伺服器的;
-
如果快取中不存在,則去請求資料庫,並且將資料庫計算出來的資料回填給快取;
-
返回資料給客戶端;
寫操作
各種情況會導致資料庫和快取出現不一致的情況,這就是快取和資料庫的雙寫一致性問題;
目前快取存在三種策略,分別是
-
Cache Aside 更新策略:同時更新快取和資料庫;
-
Read/Write Through 更新策略:先更新快取,快取負責同步更新資料庫;
-
Write Behind Caching 更新策略:先更新快取,快取定時非同步更新資料庫;
三種策略各有優缺點,可以根據業務場景使用;
Cache Aside 更新策略
該策略大概的流程就是請求過來時先從快取中取,如果命中快取的話,則直接返回讀取的資料;相反如果沒有命中的話,接著會從資料庫中成功獲取到資料後,再去清除快取中的資料;具體流程圖如下:
但是以上在某些特殊的情況下是存在問題:
問題1:先更新資料庫,後更新快取
兩個執行緒在高併發的情況下就會可能出現數據髒讀的情況:
-
執行緒A執行寫操作,成功更新資料庫;
-
執行緒B同樣執行和執行緒A一樣的操作,但是線上程A執行更新快取的過程中,執行緒B更新了新的資料庫資料到快取中;
-
執行緒A線上程B全部操作完成以後才將相對老的資料又更新到了快取中;
問題2:先刪除快取,後更新資料庫
同樣的,在高併發場景下同樣會出現髒讀的情況:
-
執行緒A成功刪除了快取,等待更新資料庫;
-
執行緒B進行讀操作,由於此時快取已經被刪除了,因此執行緒B重新從資料庫中獲取老的資料並且更新到了快取中;
-
執行緒A線上程B完成了整個的讀操作以後,才更新資料庫,此時快取中的資料依舊是老的資料;
問題3:先更新資料庫,後刪除快取
目前這是比較普遍的操作,即使它還是有可能會出現髒讀的情況:
-
執行緒A進行讀操作,此時正好沒有命中快取,接著請求資料庫;
-
執行緒B進行寫操作,線上程A沒有從資料庫中獲取到資料之前,把資料寫入到資料庫中,並且還成功刪除了快取;
-
執行緒A線上程B完成了整個的寫操作以後,才將相對老的資料更新到快取中;
但是以上的情況比較不會出現,這是因為上述情況需要滿足執行緒A的讀操作要慢於執行緒B的寫操作,但是在現實過程中,讀操作通常都是要快於寫操作得多的,但是為了避免發生以上的情況, 通常都是要給快取加上一個過期的時間 ;
但是設想一下,如果上面的刪除快取失敗了怎麼辦呢,這樣顯然會導致資料髒讀的情況,我覺得方案如下:
-
設定快取的過期時間(必須要做);
-
提供一個保障重試機制,將哪些刪除失敗的key提供給訊息佇列去消費;
-
從訊息佇列取出這些key再次進行刪除,失敗再次加入到訊息佇列中,超過一定次數以上則人工介入;
但是以上情況需要在業務程式碼中進行操作,顯然得需要進行解耦;
目前我們公司就是使用該方案,具體過程為在更新資料庫資料的時候,資料庫會以binlog日誌的形式儲存下來,通過 canal 開源軟體將binlog解析成程式語言可以解析的地步,接著訂閱程式獲取到這些資料以後,嘗試刪除快取操作,如果操作失敗的話,則將其加入到訊息佇列中,重複消費,當刪除操作的失敗次數到達一定的次數以後,還是得人工介入。
Read/Write Through 更新策略
該模式下,程式只需要維護快取即可,資料庫的同步工作交由快取來同步更新;
該策略具體又分為兩種:
-
Read Through:在查詢的過程中更新快取;
-
Write Through:在寫操作的過程中如果命中快取,則直接更新快取,資料庫則由快取自己同步去更新;
Write Behind Caching 更新策略
該策略只更新快取,不會立馬更新資料庫,只會在一定的時間非同步的批量去操作資料庫;這樣的好處在於直接操作快取,效率極高,並且操作資料是非同步的,還可以將多次的操作資料庫語句合併到一個事務中一起提交,因此效率很客觀;
但是,該策略沒有辦法做到資料強一致性,並且實現邏輯相對是比較複雜的,因為它需要確認哪些是需要更新到資料庫的,哪些是僅僅想要儲存在快取中的;
比較
目前通常使用的是第一種策略中的先更新資料庫,後更新快取;其他的相較比起來實現都比較複雜;
最後想說的是, 快取本來就是為了犧牲強一致性來提高效能的,所以肯定會存在一定的延遲時間,我們只需要保證最終的資料一致性即可;
最後
以上是我在學習過程中的總結(其中很多內容都用了其他部落格的內容),感恩~