大家好,我是冰河~~
最近小夥伴最近都在問我,在系統中引入緩存後,當向資料庫中寫入資料時,是先寫資料庫還是先寫緩存呢?先寫資料庫和先寫緩存有什麼差別嗎?今天,我們就一起來聊聊這個話題。
從本質上講,無論是先寫資料庫還是先寫緩存,都是為了保證資料庫和緩存的資料一緻,也就是我們常說的資料一緻性。
随着網際網路的高速發展,當今時代已然從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)重新從消息隊列中獲得該資料,重試操作。
好了,今天就到這兒吧,我是冰河,我們下期見~~