首先,緩存因爲其高併發和高性能的特性,已經在項目中被普遍使用。在讀取緩存方面,你們沒啥疑問,都是按照下圖的流程來進行業務操做java
可是在更新緩存方面,對於更新完數據庫,是更新緩存呢,仍是刪除緩存。又或者是先刪除緩存,再更新數據庫,其實你們存在很大的爭議mysql
本文由如下三個部分組成 一、講解緩存更新策略 二、對每種策略進行缺點分析 三、針對缺點給出改進方案redis
先作一個說明,從理論上來講,給緩存設置過時時間,是保證最終一致性的解決方案。這種方案下,咱們能夠對存入緩存的數據設置過時時間,全部的寫操做以數據庫爲準,對緩存操做只是盡最大努力便可。也就是說若是數據庫寫成功,緩存更新失敗,那麼只要到達過時時間,則後面的讀請求天然會從數據庫中讀取新值而後回填緩存。所以,接下來討論的思路不依賴於給緩存設置過時時間這個方案。 在這裏,咱們討論三種更新策略:sql
這套方案,你們是廣泛反對的。爲何呢?有以下兩點緣由。數據庫
緣由一(線程安全角度) 同時有請求A和請求B進行更新操做,那麼會出現緩存
這就出現請求A更新緩存應該比請求B更新緩存早纔對,可是由於網絡等緣由,B卻比A更早更新了緩存。這就致使了髒數據,所以不考慮。安全
緣由二(業務場景角度) 有以下兩點:網絡
接下來討論的就是爭議最大的,先刪緩存,再更新數據庫。仍是先更新數據庫,再刪緩存的問題。架構
該方案會致使不一致的緣由是。同時有一個請求A進行更新操做,另外一個請求B進行查詢操做。那麼會出現以下情形:併發
那麼,如何解決呢?採用延時雙刪策略 僞代碼以下
public void write(String key,Object data){ redis.delKey(key); db.updateData(data); Thread.sleep(1000); redis.delKey(key); }
轉化爲中文描述就是
那麼,這個1秒怎麼肯定的,具體該休眠多久呢?
針對上面的情形,讀者應該自行評估本身的項目的讀數據業務邏輯的耗時。而後寫數據的休眠時間則在讀數據業務邏輯的耗時基礎上,加幾百ms便可。這麼作的目的,就是確保讀請求結束,寫請求能夠刪除讀請求形成的緩存髒數據。
若是你用了mysql的讀寫分離架構怎麼辦?
ok,在這種狀況下,形成數據不一致的緣由以下,仍是兩個請求,一個請求A進行更新操做,另外一個請求B進行查詢操做。
採用這種同步淘汰策略,吞吐量下降怎麼辦?
ok,那就將第二次刪除做爲異步的。本身起一個線程,異步刪除。這樣,寫的請求就不用沉睡一段時間後了,再返回。這麼作,加大吞吐量。
第二次刪除,若是刪除失敗怎麼辦?
這是個很是好的問題,由於第二次刪除失敗,就會出現以下情形。仍是有兩個請求,一個請求A進行更新操做,另外一個請求B進行查詢操做,爲了方便,假設是單庫:
首先,先說一下。老外提出了一個緩存更新套路,名爲《Cache-Aside pattern》。其中就指出
另外,知名社交網站facebook也在論文《Scaling Memcache at Facebook》中提出,他們用的也是先更新數據庫,再刪緩存的策略。
這種狀況不存在併發問題麼?
不是的。假設這會有兩個請求,一個請求A作查詢操做,一個請求B作更新操做,那麼會有以下情形產生
(1)緩存恰好失效
(2)請求A查詢數據庫,得一箇舊值
(3)請求B將新值寫入數據庫
(4)請求B刪除緩存
(5)請求A將查到的舊值寫入緩存 ok,若是發生上述狀況,確實是會發生髒數據。
然而,發生這種狀況的機率又有多少呢?
發生上述狀況有一個先天性條件,就是步驟(3)的寫數據庫操做比步驟(2)的讀數據庫操做耗時更短,纔有可能使得步驟(4)先於步驟(5)。
但是,你們想一想,數據庫的讀操做的速度遠快於寫操做的(否則作讀寫分離幹嗎,作讀寫分離的意義就是由於讀操做比較快,耗資源少),所以步驟(3)耗時比步驟(2)更短,這一情形很難出現。 假設,有人非要擡槓,有強迫症,必定要解決怎麼辦?
如何解決上述併發問題?
首先,給緩存設有效時間是一種方案。其次,採用策略(2)裏給出的異步延時刪除策略,保證讀請求完成之後,再進行刪除操做。
還有其餘形成不一致的緣由麼?
有的,這也是緩存更新策略(2)和緩存更新策略(3)都存在的一個問題,若是刪緩存失敗了怎麼辦,那不是會有不一致的狀況出現麼。好比一個寫數據請求,而後寫入數據庫了,刪緩存失敗了,這會就出現不一致的狀況了。這也是緩存更新策略(2)裏留下的最後一個疑問。
如何解決? 提供一個保障的重試機制便可,這裏給出兩套方案。
方案一: 以下圖所示
流程以下所示
方案二:
流程以下圖所示:
備註說明:上述的訂閱binlog程序在mysql中有現成的中間件叫canal,能夠完成訂閱binlog日誌的功能。至於oracle中,博主目前不知道有沒有現成中間件可使用。另外,重試機制,博主是採用的是消息隊列的方式。若是對一致性要求不是很高,直接在程序中另起一個線程,每隔一段時間去重試便可,這些你們能夠靈活自由發揮,只是提供一個思路。