什麼是雙寫?
我們開門見山,這個很好了解,雙寫就是說,一份資料在資料庫存一份,在緩存中也存一份,給緩存一個過期時間,當讀不到緩存時從資料庫讀出來然後寫入緩存。
為什麼需要雙寫呢?
當請求量越來越大的時候,系統會慢慢出現瓶頸,由于資料庫的連結是有限的,無法支撐較高的qps,是以我們要想一個辦法分擔資料庫的壓力,于是就有了雙寫,将資料寫入緩存,用戶端讀取資料直接從緩存中讀取,這樣就可以提高系統的性能。
但是如果要使用雙寫,那麼不管是先更新緩存還是先更新mysql,總會有時間間隔,那麼就要保證你的業務在一定程度上允許短暫的資料不一緻的情況出現,否則,還是不建議使用的.
那麼就有人問了?雙寫一定不能保證強一緻性嗎?
答案是可以,隻要把所有與其相關的讀寫請求用隊列串行化,這樣就可以保證雙寫的強一緻性了,但是這樣會極大的降低系統的qps,非常不推薦這種做法。
既然要雙寫,那麼肯定會出現資料庫和緩存資料不一緻的情況,要怎樣去避免呢?
雙寫不一緻問題要怎麼解決
這種情況會有什麼問題呢?我們看下圖:
首先a先更新資料庫,按照正常流程來走,緊接着要a線程删除緩存,可是突然後面來了個b線程,并且a線程因為各種業務原因卡住了,導緻b線程先完成了,之後a線程才更新緩存。這時突然有其他線程進來讀資料,就會讀到a的資料,但是按照業務流程來走,應該讀到b的資料,此時,就出現了資料錯亂的問題。
1.線程a更新資料庫
2.線程b更新資料庫
3.線程b更新緩存
4.線程a更新緩存
5.其他線程讀資料(讀錯了)
到這裡我們會發現,直接更新緩存是有很大的問題的,而且很多時候,在複雜點的緩存場景,緩存不單單是資料庫中直接取出來的值,有可能是聯合其他的很多資料結合計算出來的一個值。
而且可能會有一種場景,我們經常在更新資料庫後直接更新緩存,但是在此之間并沒有緩存被通路的需求,這樣我們就做了很多無用功,付出了很多代價。
大家應該對單例模式有所了解,其中有一種懶加載的思想,就是說,在你需要的時候再去加載,用在雙寫的情況下非常合适,也就有了下面這種先更新資料庫,再删除緩存的模式。
這種情況又會有什麼問題呢?
當然,這還是一種有問題的方案,我們來跟着圖盤一盤。
1:線程a更新資料庫
2:程式挂了,沒來的及删除緩存
3.其他線程來讀資料(全都是錯的)
這種方案的問題一目了然,隻要程式挂了,就會出現資料讀錯的情況,真實的業務你是應該讀到a線程的值,卻一直在讀之前的值。
那這種方案有沒有優化呢?
當然也有了,其實我們可以每次寫入都記錄日志,然後修改結束後也記錄日志,通過日志狀态來判斷是否寫入成功,
如果沒有寫入成功後續并且沒有新的寫入請求,就補寫,
否則不做處理。
但是這種情況也會出現不一緻的問題,就是如果寫資料庫程式斷了,到下次恢複資料之前這段時間,還會出現資料不一緻的情況。
并且如果是頻繁寫入的情況,很有可能日志機制沒有發揮作用,就有新資料寫入覆寫,并且日志系統還要占用額外的資源。
我懂了!應該先删除緩存再更新資料庫,這樣就可以了!
來來來,繼續貼圖,是不是很熟悉?
這種方案會有問題嗎??當然有,繼續盤道:
1:線程a删除緩存
2:線程b删除緩存
3:線程a卡了
4:線程b更新資料庫
5:線程a更新資料
6:其他線程讀資料,讀到了a的(又錯了)
完了,這種情況居然也有問題,線程a到底行不行,每次都是你出事。
這種情況中間會有一段資料亂掉,但是随着下次的更新資料還是會恢複正确。
難道終極方案是先删除緩存,再更新資料庫,再更新緩存??
繼續貼圖
1.線程a删除緩存
2.其他線程讀取資料,讀到的是a之前的資料
3.線程a更新資料庫
4.線程a删除緩存
5.其他線程設定緩存資料,是a之前的資料(此時應該是a的)
大家是不是又發現了,這種設計方案還是會有問題的,直到下次資料更新才有可能将資料恢複正确。
來吧,最後一種大家經常讨論的延時雙删方案,我們一起盤一盤。
go on
1.先删除緩存
2.再寫資料庫
3.休眠一段時間(根據具體的業務時間來定)
4.再次删除緩存
這裡加了一個延時的操作,目的是確定 修改資料庫 -> 清空緩存前,其他事務的更改緩存操作已經執行完。
所有的寫操作以資料庫為準,隻要到達緩存過期時間,則後面的讀請求自然會從資料庫中讀取新值然後回填緩存。
但這其中難免還是會大量的查詢到舊緩存資料的,因為延時時間是根據業務自己定義的,時間太長和太短在高并發情況下都會有查詢到髒資料的情況産生。
這樣最差的情況就是在逾時時間内資料存在不一緻。