「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」html
微信公衆號搜索【程序媛小莊】,Rest cannot be enjoyed by lazy people~前端
因爲緩存的高併發和高性能已經在各類項目中被普遍使用,在讀取緩存這方面基本都是一致的,大概都是按照下圖的流程進行操做:python
可是在更新緩存方面,是更新完數據庫再更新緩存仍是直接刪除緩存呢?又或者是先刪除緩存再更新數據庫?在這一點上就值得探討了。mysql
在實際項目開發中須要保證數據庫和緩存中的數據一致,不然人家充值了100塊,不斷刷新卻仍是顯示0.01元,豈不是尷尬?從理論上來講,爲緩存設置過時時間是最終保證數據一致性的解決方案,採用這種方案的話,全部的寫操做都是以數據庫爲準,若是數據庫寫入成功可是緩存更新失敗,只要緩存到過時時間以後後面讀緩存時天然會在數據庫中讀取新的值而後更新緩存。接下來探討的思路主要的方向是在不依賴爲緩存設置過時時間的前提下如何保證數據一致性。這裏主要探討三種方案:redis
①先更新數據庫,再更新緩存sql
②先刪除緩存,再更新數據庫數據庫
③先更新數據庫,再刪除緩存後端
這種方案是廣泛被反對的(在個人認知範圍中~),爲啥呢?爲啥這種方案就被反對呢?緣由主要有兩方面,請聽我細細道來:緩存
首先從數據安全方面考慮,若是同時有請求A和請求B同時進行操做,A先更新了數據庫的一條數據,隨後B立刻有更新了該條數據,可是可能由於網絡延遲等緣由,B卻比A先更新了緩存,就會出現一種什麼狀況呢?緩存中的數據並不最新的B更新過的數據,就致使了數據不一致的狀況。安全
其次從業務場景方面考慮,若是是一個寫數據庫較多而讀數據庫較少的業務,若是採用這種方案就會致使數據還沒讀緩存就會被頻繁更新,白白浪費性能。
綜合以上兩方面的考慮,這種方案果斷pass。下面的兩種方案就是爭議較大的兩種方案了,究竟是先刪緩存再更新數據庫仍是先更新數據庫再刪除緩存?
若是同時有一個請求A進行更新操做,請求B進行查詢操做,就可能會出現A請求進行寫操做前會刪除緩存,B請求恰好此時進來發現緩存是空的,B請求就會查詢數據庫,若是此時A請求的寫操做還未完成,B請求查詢到的就仍是舊的值,仍是會將舊的值寫入緩存,A請求將新的值寫入數據庫,此時就會致使數據不一致的問題,若是不採用給緩存設置過時時間的策略,該數據永遠都是髒數據。
解決這種狀況能夠採用延時雙刪的策略,就是在更新數據庫以前先刪除緩存,而後對數據庫進行寫入操做,數據庫更新完成以後再次進行刪除緩存的操做,目的是刪除讀請求可能形成的緩存髒數據,第二次刪除緩存以前能夠休眠幾秒,具體時間開發者能夠評估一下本身項目讀數據業務邏輯的耗時,而後在該耗時基礎上加幾百ms便可,這麼作的目的就是確保讀請求結束寫請求能夠刪除讀請求形成的髒數據。若是MySQL採用的是讀寫分離的架構,可能因爲主從延時的緣由形成數據不一致,能夠在寫操做完成以後根據主從延時時間休眠一下而後再進行刪除緩存的操做。延時雙刪的僞代碼以下:
# 僞代碼
def delay_delete():
redis.delete('name') # 更新數據庫以前先刪除緩存
sql = 'update info set name='lili' where id=1;' # 更新數據庫
cursor.execute(sql)
time.sleep(1) # 若是mysql是主從架構則休眠主從延時的時間再多幾百ms
redis.delete('name') # 再次刪除緩存
複製代碼
那會不會存在第二次刪除緩存失敗的狀況呢?若是第二次刪除失敗,仍是會形成緩存和數據庫不一致的問題,又如何解決呢?且看下一種方案。
老外提出了一個緩存更新方案 ,文章中提到**應用程序應該從cache中獲取數據,若是獲取成功直接返回,若是沒有獲取成功,則從數據庫中獲取,成功後放到緩存中,更新數據時應該先把數據存到數據庫中成功後再讓緩存失效。**原文以下
If an application updates information, it can follow the write-through strategy by making the modification to the data store, and by invalidating the corresponding item in the cache.
When the item is next required, using the cache-aside strategy will cause the updated data to be retrieved from the data store and added back into the cache.
這種方案會不會產生數據不一致的狀況呢?好比下述這種狀況:
有兩個請求A和B,A進行查詢同時B進行更新,假設發生下述狀況:
①此時緩存恰好失效
②請求A 就會去查詢數據庫獲得一箇舊的值
③請求B將新的值寫入數據庫
④請求B寫入成功後刪除緩存
⑤請求A將查到的機制寫入緩存,產生髒數據...
若是發聲上述狀況,確實會產生數據不一致的狀況,可是XDM想想,發生這種狀況的機率是多少呢?若是先要產生這種結果,就必須有一個條件,就是請求B的操做時間很是短,短到什麼程度呢,就是請求B寫入數據庫的操做要比請求A從數據庫中讀取數據的速度要快(由於redis很是快,所以操做redis的時間能夠暫且忽略),只有這種狀況下④纔可能比⑤先發聲,可是數據庫的讀操做要遠比寫操做快的多,否則作讀寫分離幹嗎呢?因此這種狀況發生的機率是很是很是很是的低,可是若是強迫症患者出現必需要解決怎麼辦呢?就能夠採用給緩存設置過時時間或者採用第二種方案的延時雙刪策略,保證讀請求完成以後在進行刪除操做。
還有問題呀,就是最終解決方案三可能 出現的極低機率的數據不一致的方案是採用方案二的延時雙刪策略,但是在方案二中也說了,若是出現緩存刪除失敗的狀況咋辦?那不是還會出現數據不一致的問題嗎?這個問題到底如何解決呢?這裏提供一個重試機制,刪除失敗就重試一次唄,這裏提供一種重試的方案。
①更新數據庫
②因爲各類緣由緩存刪除失敗
③將刪除失敗的緩存放入消息隊列中
④業務代碼從消息隊列中獲取須要刪除的key
⑤繼續嘗試刪除操做,直到成功
文章首發於微信公衆號程序媛小莊,同步於掘金。
碼字不易,轉載請說明出處,走過路過的小夥伴們伸出可愛的小指頭點個贊再走吧(╹▽╹)