前言
看到标題,可能小夥伴們會虎軀一震?嗯?難道不應該使用Redis做緩存?
答:
不是你想的那樣,
隻是說,有幾種情況,使用緩存我們需要了解考慮周全,選擇正确的使用姿勢。
正文
好,我們進入該篇正題。
(一定要耐心結合我舉例進行推演才能更加明白)
我們既然選擇了緩存,用redis存儲緩存資料,必然是為了一個字,快。
就是想避免每次都通路資料庫,能直接從緩存很快地拿出資料。
那麼,大家在使用緩存的時候,會不會冥冥之中有對資料的準确性有過懷疑? 對資料的時效性有過質疑?
先列出,我們使用緩存的時候,會選擇到的方式 四種:
(對于讀操作,不用多言,那肯定是先讀緩存,沒有才從資料庫擷取。)
是以着重針對 寫 操作 分析:
第一種:
先更新資料庫,再更新緩存
第二種:
先更新緩存,再更新資料庫
第三種:
先删除緩存,再更新資料庫
第四種:
先更新資料庫,再删除緩存
逐一分析可能存在的問題
先更新資料庫,再更新緩存
存在問題1:
-假設原來的值是500,先需要更新資料庫的值變為1000, 成功了; 資料庫的值為 1000;
-接着就會去更新緩存, 因為未知原因(網絡等),導緻更新失敗; 緩存的值為 500不變。
-那麼讀操作來了,先去緩存裡面讀資料,拿到的是 500, 無疑這是讀到了舊的資料。
存在問題2:
高并發場景時,
-假設原來的值是500,線程A更新資料庫的值變為1000, 成功了; 資料庫的值為 1000,接着就準備去更新緩存;
-這時候,線程B來了,更新資料庫的值變成200,接着就會去更新緩存;
-因為網絡的波動原因,B線程從後追上A線程, 率先更新了緩存的值,200; 此時緩存資料為200,非常正确。
-但緊接着,A線程緩過神來了,把緩存的值更新為了1000;
-這時候,讀操作來了,先讀緩存,拿出來的值是1000,實際上應該是200,無疑這是讀到了錯誤資料。
特别是寫操作遠大于讀操作的項目場景, 這個還是很讓人頭疼的。
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
先更新緩存,再更新資料庫
存在問題1:
-假設原來的值是500,先需要更新緩存的值1000,成功了;緩存的值為1000;
-接着就會去更新資料庫, 因為未知原因(網絡等),導緻更新失敗; 資料庫的值為 500不變。
-那麼讀操作來了,先去緩存裡面讀資料,拿到的是1000,可是資料庫是500 。無疑這是讀到了錯誤資料。
因為資料庫更新不成功,緩存的資料應該也是不可以成功的。
存在問題2:
高并發場景時,
-假設原來的值是500,線程A更新緩存的值變為1000,成功了;緩存的值為1000,,接着就準備去更新資料庫;
-這時候,線程B來了,更新更新緩存的值變成200,接着就會去更新資料庫;
-因為網絡的波動原因,B線程從後追上A線程, 率先更新了資料庫,200; 此時資料庫資料為200。
-但緊接着,A線程緩過神來了,把資料庫的值的值更新為了1000;
-此時此刻,緩存資料是200,資料庫資料是1000; 讀操作來了,無疑這是讀到了錯誤資料。
特别是寫操作遠大于讀操作的項目場景, 這個還是很讓人頭疼的。
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
看完上面2種情況,明顯我們都知道,在高并發切寫遠多于讀的時候,這兩種情況都是很不可取的;
是以,就衍生出一個政策,在寫操作的時候,不要去更新緩存,而且選擇直接删除緩存。
更新緩存的操作,放在讀操作進行;
讀操作為:如果緩存有,讀出;無,讀出資料庫的值,更新緩存。
ps:當然讀多寫少的場景,上面2種方式也都還行。
那就是 先删除緩存,再更新資料庫 還是 先更新資料庫,再删除緩存
先删除緩存,再更新資料庫
存在問題1:
-假設原來緩存的值是500,資料庫也是500;
-先删除緩存, 因為未知原因(網絡等),導緻更新資料庫時失敗;
-這時候,讀操作來了,無影響,因為緩存沒有,直接讀資料庫,正常;
-這時候,寫操作又來,無影響,緩存已經沒有,繼續更新資料庫,正常;
這麼一看,好像還蠻不錯!
然而并不然!因為删緩存的政策代替更新緩存,上面講到了是把緩存寫入操作給了讀操作進行。
繼續看高并發的場景。
存在問題2:
高并發場景時,
-假設原來緩存的值是500,資料庫也是500;
-線程A删掉了緩存,正準備去更新資料庫,把值變成1000;
-因為網絡的波動原因線程A懵了,呆滞了;
-這時候,線程B來了,線程B進行的讀操作,查詢,一看緩存沒資料了(被線程A删掉了),接着就去讀資料庫,值為500;
-線程B讀完資料庫的值500後,緊接着把資料寫入緩存! 此時緩存的資料是500;
-此時線程A緩過神來,更新資料庫值為1000。
-此後,緩存的資料是500,資料庫資料是1000, 無疑,又是存在錯誤資料!
特别是讀操作遠大于寫操作的項目場景, 這個好像又開始很讓人頭疼了。
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
先更新資料庫,再删除緩存
存在問題1:
-假設原來緩存的值是500,資料庫也是500;更新資料庫值變成1000,成功了;
-删除緩存失敗,還是500;
-這時候,讀操作來了,讀出緩存的資料500,無疑這是讀到了錯誤資料;
存在問題2:
高并發場景時,
-假設原來緩存的值是500,資料庫也是500;
-線程A來了,更新資料庫的值為1000,删除了緩存;
-這時候,線程B也來了,線程B是一個讀操作,發現緩存沒有,讀取了資料庫的值1000,然後準備寫入緩存;
-因為網絡的波動原因線程B懵了,呆滞了;
-這時候線程C來了,線程C更新資料庫值為2000,然後很利索删除緩存(這時候其實本來就沒緩存);
-然後線程B緩過神來了,很利索把值1000寫入了緩存。
-這時候,其他讀操作來了,讀出換成的資料1000,而資料庫實際是2000,無疑這是讀到了錯誤資料;
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
******************************************懶人手動分割線******************************************
???
嗯?什麼?
一輪下來把四種姿勢都看完了,發現都不好???
那咱們總得選一種用緩存啊?
如果項目場景是讀多寫少 :
而且是并發不考慮的場景,其實更新緩存的方式也是能用一用,不過還是建議用下面的删除緩存的方式。
如果項目場景是寫多讀少 :
其實這時候,使用删除緩存的政策顯然好很多。
先删除緩存,再更新資料庫 分析:
那麼選擇 如果 先删除緩存,再更新資料庫 ,上面提到了,就是怕高并發的場景,導緻資料庫的資料是正常的,而緩存的資料是不對的。
既然是因為這個緩存資料是髒的,那麼針對這個問題,于是乎有了 延時雙删除政策:
先删除 緩存 ,再更新資料庫, 延時後再删除緩存
從字面上其實已經能知道,就是補一刀把後續的髒緩存資料删掉,這麼具體的延時時間是多少,就得根據具體項目業務時間去衡量了。
先更新資料庫,再删除緩存 分析:
那麼選擇 如果 先更新資料庫 ,再删除緩存 ,其實這個方式是 一個國外比較推薦的使用緩存方式 : Cache-Aside pattern
上面提到了,這種方式在高并發的場景也是存在問題的。
但是為什麼外國人這麼推薦這種方式呢?
回顧這種方式,在高并發的情形,出現問題的原因是 讀操作的後面寫入資料到緩存的環節上。
但是讀操作實際上肯定比寫操作快得多,是以發生上邊描述的出現髒資料的場景的機率也是比較小。
每次讀操作的時候最後會将資料庫資料寫到緩存, 而寫操作最後會删除調緩存。
我們想象下,讀非常快,寫稍微慢的場景, 這樣就算有讀操作寫入的舊的緩存資料,也會被慢寫操作的删掉。
這樣碰巧發生出現不一緻資料的機率就會小很多,這也是外國人推薦的原因。
最後來一個個人的總結:
并發稍微低,那麼我們可以用 先删除緩存,再更新資料庫 再配上延時雙删除政策。
并發稍微高,那麼我們可以用 Cache-Aside pattern , 先更新資料庫 ,再删除緩存 。
其實看完這篇的,大家都知道,髒資料 在不适用 分布式鎖 或者 其他能保證資料順序的方法的情況下,都是存在的。
隻不過是出現這種髒資料的機率以及嚴重性,是否是項目的業務需求可以接收。
對于該篇文章分析存在的問題,都是有額外的補救方法,如加鎖,重試,消息隊列等等
好,該篇介紹就到此吧。