大家好,我是冰河~~
最近小夥伴最近都在問我,在系統中引入快取後,當向資料庫中寫入資料時,是先寫資料庫還是先寫快取呢?先寫資料庫和先寫快取有什麼區別嗎?今天,我們就一起來聊聊這個話題。
從本質上講,無論是先寫資料庫還是先寫快取,都是為了保證資料庫和快取的資料一致,也就是我們常說的資料一致性。
隨著網際網路的高速發展,當今時代已然從IT時代進入到DT時代。網際網路系統架構也已經由最初的單體架構轉變為分散式、微服務架構模式。從資料體量上來看,各系統儲存的資料量越來越大,資料的查詢效能越來越低。此時,就需要我們不斷的進行優化,一種常用的優化手段就是引入快取。而引入快取後,我們在向資料庫插入資料時,到底是先更新資料庫還是先更新快取呢?
快取的一般使用
快取,從本質上講,是為了更好的協調兩個速度差異比較大的元件而引入的一種中間快取層。例如,如果需要將資料讀入CPU進行計算處理,由於CPU的運算速度是非常快的,而磁碟的IO處理相比於CPU來說,慢了很多數量級,每次從磁碟讀取資料,勢會造成CPU長時間並且頻繁等待磁碟IO。此時,我們就可以通過記憶體來緩和CPU和磁碟之間的速度差異。
從快取的使用上來說,一般是按照如下的流程來使用快取。
我們也可以表示成如下的序列圖。
在上面的使用示例中,我們只是簡單的將資料放入了快取,最多為快取設定一個過期時間,到期後,快取自然就會被清除,後續的請求由於在快取中獲取不到資料,又會從資料庫中獲取資料,將資料寫入快取。
但是在後續更新資料的操作中,是更新完資料庫,接下來更新快取還是刪除快取?又或者是先刪除快取,再更新資料庫?
快取更新策略
從理論上來說,給快取設定過期時間,其實是一中最終一致性的表現。這種方案下,可以對存入快取的資料設定過期時間,所有的寫操作以資料庫為準,對快取操作只是盡最大努力即可。也就是說如果資料庫寫成功,快取更新失敗,那麼只要到達過期時間,則後面的讀請求自然會從資料庫中讀取新值然後回填快取。這也是一般情況下,使用的最多的一種方式。
先更新資料庫再更新快取
其實,這種方案很多有經驗的小夥伴是很反對的,為啥,我們來分析下。
首先,這種方案會有執行緒安全的問題。
例如,同時有執行緒A和執行緒B對資料進行更新操作,可能會出現下面的執行順序。
(1) 執行緒A更新了資料庫
(2) 執行緒B更新了資料庫
(3) 執行緒B更新了快取
(4) 執行緒A更新了快取
此時就會出現資料庫中的資料與快取的資料不一致的情況,這是因為執行緒A先更新了資料庫,可能因為網路等異常情況,執行緒B更新完資料庫進而更新了快取,當執行緒B更新完快取後,執行緒A才更新快取,這就導致了資料庫資料與快取資料的不一致。
其次,這種方案也有其不適用的業務場景。
首先一個業務場景就是資料庫寫多讀少的場景,這種場景下采用先更新資料庫再更新快取的策略,就會導致快取並未被讀取就會被頻繁的更新,極大的浪費了伺服器的效能。
再一個業務場景就是資料庫中的資料不是直接寫入快取的,而是需要大量的複雜運算,將運算結果寫入快取。如果這種場景下使用先更新資料庫再更新快取的策略,也會造成伺服器資源的浪費。
先刪除快取再更新資料庫
先刪除快取再更新資料庫的方案也存在著執行緒安全的問題,例如,執行緒A更新快取,同時,執行緒B讀取快取的資料。可能會出現下面的執行順序。
(1) 執行緒A刪除快取
(2) 執行緒B查詢快取,發現快取中沒有想要的資料
(3) 執行緒B查詢資料庫中的舊資料
(4) 執行緒B將查詢到的舊資料寫入快取
(5) 執行緒A將新資料寫入資料庫
此時,就出現了資料庫中的資料和快取中的資料不一致的情況。如果刪除快取失敗,也會出現資料庫資料和快取資料不一致的現象。
先更新資料庫再刪除快取
首先,這種方式也有極小的概率發生資料庫資料和快取資料不一致的情況,例如,執行緒A做查詢操作,執行緒B執行更新操作,其執行的順序如下所示。
(1)快取剛好失效
(2)請求A查詢資料庫,獲取到資料庫中的舊值
(3)請求B將新值寫入資料庫
(4)請求B刪除快取
(5)請求A將查到的舊值寫入快取
如果上述順序一旦發生,就會造成資料庫中的資料和快取中的資料不一致的情況發生。
但是,先更新資料庫再刪除快取的策略發生資料庫和快取資料不一致的概率很低,原因就是:(3)的寫資料庫操作比步驟(2)的讀資料庫操作耗時更短,才有可能使得步驟(4)先於步驟(5)執行。但是,往往資料庫的讀操作的速度遠快於寫操作,因此步驟(3)耗時比步驟(2)更短,這一場景很難出現。
如果刪除快取失敗,也會出現資料庫資料和快取資料不一致的現象。
這樣說來,貌似三種方案都不安全呀,那我們該如何做呢?最終要的就是需要引入重試機制。
推薦使用
在實際的生產環境中,推薦 使用先更新資料庫再刪除快取 的操作。那麼,我們該如何解決這種策略下的問題呢?
有兩種方案,一種是在程式邏輯中處理失敗重試的操作;另外,藉助於阿里巴巴開源的Canal。
手動失敗重試
流程如下所示
(1)更新資料庫資料;
(2)刪除快取資料失敗
(3)將需要刪除的key傳送至訊息佇列
(4)自己消費訊息,獲得需要刪除的key
(5)繼續重試刪除操作,直到成功
這種方案有一個缺點,對業務線程式碼造成大量的侵入。
同步資料庫資料
先來一張圖,這種圖從整體架構上解決了資料庫資料和快取資料不一致的情況。
流程如下圖所示:
(1)更新資料庫資料
(2)資料庫將資料表資料的變更資訊寫入binlog日誌當中
(3)訂閱程式獲取所需要的資料以及key
(4)程式邏輯中處理具體的業務邏輯,接收訂閱binlog、發起刪除快取的請求。
(5)嘗試刪除快取操作,發現刪除失敗
(6)將這些資訊傳送至訊息佇列
(7)重新從訊息佇列中獲得該資料,重試操作。
好了,今天就到這兒吧,我是冰河,我們下期見~~