分佈式之數據庫和緩存雙寫一致性方案解析

引言mysql

爲何寫這篇文章?redis

首先,緩存因爲其高併發和高性能的特性,已經在項目中被普遍使用。在讀取緩存方面,你們沒啥疑問,都是按照下圖的流程來進行業務操做。
image
可是在更新緩存方面,對於更新完數據庫,是更新緩存呢,仍是刪除緩存。又或者是先刪除緩存,再更新數據庫,其實你們存在很大的爭議。目前沒有一篇全面的博客,對這幾種方案進行解析。因而博主戰戰兢兢,頂着被你們噴的風險,寫了這篇文章。sql

文章結構數據庫

本文由如下三個部分組成
一、講解緩存更新策略
二、對每種策略進行缺點分析
三、針對缺點給出改進方案緩存

正文安全

先作一個說明,從理論上來講,給緩存設置過時時間,是保證最終一致性的解決方案。這種方案下,咱們能夠對存入緩存的數據設置過時時間,全部的寫操做以數據庫爲準,對緩存操做只是盡最大努力便可。也就是說若是數據庫寫成功,緩存更新失敗,那麼只要到達過時時間,則後面的讀請求天然會從數據庫中讀取新值而後回填緩存。所以,接下來討論的思路不依賴於給緩存設置過時時間這個方案。
在這裏,咱們討論三種更新策略:網絡

先更新數據庫,再更新緩存
先刪除緩存,再更新數據庫
先更新數據庫,再刪除緩存
應該沒人問我,爲何沒有先更新緩存,再更新數據庫這種策略。架構

(1)先更新數據庫,再更新緩存併發

這套方案,你們是廣泛反對的。爲何呢?有以下兩點緣由。
緣由一(線程安全角度)
同時有請求A和請求B進行更新操做,那麼會出現
(1)線程A更新了數據庫
(2)線程B更新了數據庫
(3)線程B更新了緩存
(4)線程A更新了緩存
這就出現請求A更新緩存應該比請求B更新緩存早纔對,可是由於網絡等緣由,B卻比A更早更新了緩存。這就致使了髒數據,所以不考慮。
緣由二(業務場景角度)
有以下兩點:
(1)若是你是一個寫數據庫場景比較多,而讀數據場景比較少的業務需求,採用這種方案就會致使,數據壓根還沒讀到,緩存就被頻繁的更新,浪費性能。
(2)若是你寫入數據庫的值,並非直接寫入緩存的,而是要通過一系列複雜的計算再寫入緩存。那麼,每次寫入數據庫後,都再次計算寫入緩存的值,無疑是浪費性能的。顯然,刪除緩存更爲適合。異步

接下來討論的就是爭議最大的,先刪緩存,再更新數據庫。仍是先更新數據庫,再刪緩存的問題。

(2)先刪緩存,再更新數據庫

該方案會致使不一致的緣由是。同時有一個請求A進行更新操做,另外一個請求B進行查詢操做。那麼會出現以下情形:
(1)請求A進行寫操做,刪除緩存
(2)請求B查詢發現緩存不存在
(3)請求B去數據庫查詢獲得舊值
(4)請求B將舊值寫入緩存
(5)請求A將新值寫入數據庫
上述狀況就會致使不一致的情形出現。並且,若是不採用給緩存設置過時時間策略,該數據永遠都是髒數據。
那麼,如何解決呢?採用延時雙刪策略
僞代碼以下

public void write(String key,Object data){

redis.delKey(key);
    db.updateData(data);
    Thread.sleep(1000);
    redis.delKey(key);
}

轉化爲中文描述就是(1)先淘汰緩存(2)再寫數據庫(這兩步和原來同樣)(3)休眠1秒,再次淘汰緩存這麼作,能夠將1秒內所形成的緩存髒數據,再次刪除。那麼,這個1秒怎麼肯定的,具體該休眠多久呢?針對上面的情形,讀者應該自行評估本身的項目的讀數據業務邏輯的耗時。而後寫數據的休眠時間則在讀數據業務邏輯的耗時基礎上,加幾百ms便可。這麼作的目的,就是確保讀請求結束,寫請求能夠刪除讀請求形成的緩存髒數據。若是你用了mysql的讀寫分離架構怎麼辦?ok,在這種狀況下,形成數據不一致的緣由以下,仍是兩個請求,一個請求A進行更新操做,另外一個請求B進行查詢操做。(1)請求A進行寫操做,刪除緩存(2)請求A將數據寫入數據庫了,(3)請求B查詢緩存發現,緩存沒有值(4)請求B去從庫查詢,這時,尚未完成主從同步,所以查詢到的是舊值(5)請求B將舊值寫入緩存(6)數據庫完成主從同步,從庫變爲新值上述情形,就是數據不一致的緣由。仍是使用雙刪延時策略。只是,睡眠時間修改成在主從同步的延時時間基礎上,加幾百ms。採用這種同步淘汰策略,吞吐量下降怎麼辦?ok,那就將第二次刪除做爲異步的。本身起一個線程,異步刪除。這樣,寫的請求就不用沉睡一段時間後了,再返回。這麼作,加大吞吐量。第二次刪除,若是刪除失敗怎麼辦?這是個很是好的問題,由於第二次刪除失敗,就會出現以下情形。仍是有兩個請求,一個請求A進行更新操做,另外一個請求B進行查詢操做,爲了方便,假設是單庫:(1)請求A進行寫操做,刪除緩存(2)請求B查詢發現緩存不存在(3)請求B去數據庫查詢獲得舊值(4)請求B將舊值寫入緩存(5)請求A將新值寫入數據庫(6)請求A試圖去刪除請求B寫入對緩存值,結果失敗了。ok,這也就是說。若是第二次刪除緩存失敗,會再次出現緩存和數據庫不一致的問題。如何解決呢?具體解決方案,且看博主對第(3)種更新策略的解析。

相關文章
相關標籤/搜索