www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 小林coding
[導(dǎo)讀]??一天,老板說「最近公司的用戶越來越多了,但是服務(wù)器的訪問速度越來越差的,阿旺幫我優(yōu)化下,做好了給你畫個餅!」。程序員阿旺聽到老板口中的「畫餅」后就非常期待,沒有任何猶豫就接下了老板給的這個任務(wù)。阿旺登陸到了服務(wù)器,經(jīng)過一番排查后,確認服務(wù)器的性能瓶頸是在數(shù)據(jù)庫。這好辦,給服務(wù)...

?

?一天,老板說「最近公司的用戶越來越多了,但是服務(wù)器的訪問速度越來越差的,阿旺幫我優(yōu)化下,做好了給你畫個餅!」。

程序員阿旺聽到老板口中的「畫餅」后就非常期待,沒有任何猶豫就接下了老板給的這個任務(wù)。阿旺登陸到了服務(wù)器,經(jīng)過一番排查后,確認服務(wù)器的性能瓶頸是在數(shù)據(jù)庫。這好辦,給服務(wù)器加上 Redis,讓其作為數(shù)據(jù)庫的緩存。這樣,在客戶端請求數(shù)據(jù)時,如果能在緩存中命中數(shù)據(jù),那就查詢緩存,不用在去查詢數(shù)據(jù)庫,從而減輕數(shù)據(jù)庫的壓力,提高服務(wù)器的性能。1阿旺有了這個想法后,就準備開始著手優(yōu)化服務(wù)器,但是擋在在他前面的是這樣的一個問題。
由于引入了緩存,那么在數(shù)據(jù)更新時,不僅要更新數(shù)據(jù)庫,而且要更新緩存,這兩個更新操作存在前后的問題
  • 先更新數(shù)據(jù)庫,再更新緩存;

  • 先更新緩存,再更新數(shù)據(jù)庫;

阿旺沒想到太多,他覺得最新的數(shù)據(jù)肯定要先更新數(shù)據(jù)庫,這樣才可以確保數(shù)據(jù)庫里的數(shù)據(jù)是最新的,于是他就采用了「先更新數(shù)據(jù)庫,再更新緩存」的方案。阿旺經(jīng)過幾個夜晚的折騰,終于「優(yōu)化好了服務(wù)器」,然后就直接上線了,自信心滿滿跑去跟老板匯報。老板不懂技術(shù),自然也沒多慮,就讓后續(xù)阿旺觀察下服務(wù)器的情況,如果效果不錯,就跟阿旺談畫餅的事情。阿旺觀察了好幾天,發(fā)現(xiàn)數(shù)據(jù)庫的壓力大大減少了,訪問速度也提高了不少,心想這事肯定成的了。好景不長,突然老板收到一個客戶的投訴,客戶說他剛發(fā)起了兩次更新年齡的操作,但是顯示的年齡確還是第一次更新時的年齡,而第二次更新年齡并沒有生效。老板立馬就找了阿旺,訓(xùn)斥著阿旺說:「這么簡單的更新操作,都有 bug?我臉往哪兒放?你的餅還要不要了?」聽到自己準備到手的餅要沒了的阿旺瞬間就慌了,立馬登陸服務(wù)器排查問題,阿旺查詢緩存和數(shù)據(jù)庫的數(shù)據(jù)后發(fā)現(xiàn)了問題。數(shù)據(jù)庫的數(shù)據(jù)是客戶第二次更新操作的數(shù)據(jù),而緩存確還是第一次更新操作的數(shù)據(jù),也就是出現(xiàn)了數(shù)據(jù)庫和緩存的數(shù)據(jù)不一致的問題。這個問題可大了,阿旺經(jīng)過一輪的分析,造成緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致的現(xiàn)象,是因為并發(fā)問題!

先更新數(shù)據(jù)庫,再更新緩存

舉個例子,比如「請求 A 」和「請求 B 」兩個請求,同時更新「同一條」數(shù)據(jù),則可能出現(xiàn)這樣的順序:
A 請求先將數(shù)據(jù)庫的數(shù)據(jù)更新為 1,然后在更新緩存前,請求 B 將數(shù)據(jù)庫的數(shù)據(jù)更新為 2,緊接著也把緩存更新為 2,然后 A 請求更新緩存為 1。此時,數(shù)據(jù)庫中的數(shù)據(jù)是 2,而緩存中的數(shù)據(jù)卻是 1,出現(xiàn)了緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致的現(xiàn)象。

先更新緩存,再更新數(shù)據(jù)庫

那換成「先更新緩存,再更新數(shù)據(jù)庫」這個方案,還會有問題嗎?依然還是存在并發(fā)的問題,分析思路也是一樣。假設(shè)「請求 A 」和「請求 B 」兩個請求,同時更新「同一條」數(shù)據(jù),則可能出現(xiàn)這樣的順序:
A 請求先將緩存的數(shù)據(jù)更新為 1,然后在更新數(shù)據(jù)庫前,B 請求來了, 將緩存的數(shù)據(jù)更新為 2,緊接著把數(shù)據(jù)庫更新為 2,然后 A 請求將數(shù)據(jù)庫的數(shù)據(jù)更新為 1。此時,數(shù)據(jù)庫中的數(shù)據(jù)是 1,而緩存中的數(shù)據(jù)卻是 2,出現(xiàn)了緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致的現(xiàn)象。所以,無論是「先更新數(shù)據(jù)庫,再更新緩存」,還是「先更新緩存,再更新數(shù)據(jù)庫」,這兩個方案都存在并發(fā)問題,當兩個請求并發(fā)更新同一條數(shù)據(jù)的時候,可能會出現(xiàn)緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致的現(xiàn)象。2阿旺定位出問題后,思考了一番后,決定在更新數(shù)據(jù)時,不更新緩存,而是刪除緩存中的數(shù)據(jù)。然后,到讀取數(shù)據(jù)時,發(fā)現(xiàn)緩存中沒了數(shù)據(jù)之后,再從數(shù)據(jù)庫中讀取數(shù)據(jù),更新到緩存中。阿旺想的這個策略是有名字的,是叫 Cache Aside 策略,中文是叫旁路緩存策略。該策略又可以細分為「讀策略」和「寫策略」。
寫策略的步驟:
  • 更新數(shù)據(jù)庫中的數(shù)據(jù);

  • 刪除緩存中的數(shù)據(jù)。

讀策略的步驟:
  • 如果讀取的數(shù)據(jù)命中了緩存,則直接返回數(shù)據(jù);

  • 如果讀取的數(shù)據(jù)沒有命中緩存,則從數(shù)據(jù)庫中讀取數(shù)據(jù),然后將數(shù)據(jù)寫入到緩存,并且返回給用戶。

阿旺在想到「寫策略」的時候,又陷入更深層次的思考,到底該選擇哪種順序呢?
  • 先刪除緩存,再更新數(shù)據(jù)庫;

  • 先更新數(shù)據(jù)庫,再刪除緩存。

阿旺這次經(jīng)過上次教訓(xùn),不再「想當然」的亂選方案,因為老板這次給的餅很大啊,必須把握住。于是阿旺用并發(fā)的角度來分析,看看這兩種方案哪個可以保證數(shù)據(jù)庫與緩存的數(shù)據(jù)一致性。

先刪除緩存,再更新數(shù)據(jù)庫

阿旺還是以用戶表的場景來分析。假設(shè)某個用戶的年齡是 20,請求 A 要更新用戶年齡為 21,所以它會刪除緩存中的內(nèi)容。這時,另一個請求 B 要讀取這個用戶的年齡,它查詢緩存發(fā)現(xiàn)未命中后,會從數(shù)據(jù)庫中讀取到年齡為 20,并且寫入到緩存中,然后請求 A 繼續(xù)更改數(shù)據(jù)庫,將用戶的年齡更新為 21。
最終,該用戶年齡在緩存中是 20(舊值),在數(shù)據(jù)庫中是 21(新值),緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致??梢钥吹?,先刪除緩存,再更新數(shù)據(jù)庫,在「讀 寫」并發(fā)的時候,還是會出現(xiàn)緩存和數(shù)據(jù)庫的數(shù)據(jù)不一致的問題。

先更新數(shù)據(jù)庫,再刪除緩存

繼續(xù)用「讀 寫」請求的并發(fā)的場景來分析。假如某個用戶數(shù)據(jù)在緩存中不存在,請求 A 讀取數(shù)據(jù)時從數(shù)據(jù)庫中查詢到年齡為 20,在未寫入緩存中時另一個請求 B 更新數(shù)據(jù)。它更新數(shù)據(jù)庫中的年齡為 21,并且清空緩存。這時請求 A 把從數(shù)據(jù)庫中讀到的年齡為 20 的數(shù)據(jù)寫入到緩存中。
最終,該用戶年齡在緩存中是 20(舊值),在數(shù)據(jù)庫中是 21(新值),緩存和數(shù)據(jù)庫數(shù)據(jù)不一致。從上面的理論上分析,先更新數(shù)據(jù)庫,再刪除緩存也是會出現(xiàn)數(shù)據(jù)不一致性的問題,但是在實際中,這個問題出現(xiàn)的概率并不高。因為緩存的寫入通常要遠遠快于數(shù)據(jù)庫的寫入,所以在實際中很難出現(xiàn)請求 B 已經(jīng)更新了數(shù)據(jù)庫并且刪除了緩存,請求 A 才更新完緩存的情況。而一旦請求 A 早于請求 B 刪除緩存之前更新了緩存,那么接下來的請求就會因為緩存不命中而從數(shù)據(jù)庫中重新讀取數(shù)據(jù),所以不會出現(xiàn)這種不一致的情況。所以,「先更新數(shù)據(jù)庫 再刪除緩存」的方案,是可以保證數(shù)據(jù)一致性的。而且阿旺為了確保萬無一失,還給緩存數(shù)據(jù)加上了「過期時間」,就算在這期間存在緩存數(shù)據(jù)不一致,有過期時間來兜底,這樣也能達到最終一致。阿旺思考到這一步后,覺得自己真的是個小天才,因為他竟然想到了個「天衣無縫」的方案,他二話不說就采用了這個方案,又經(jīng)過幾天的折騰,終于完成了。他自信滿滿的向老板匯報,已經(jīng)解決了上次客戶的投訴的問題了。老板覺得阿旺這小伙子不錯,這么快就解決問題了,然后讓阿旺在觀察幾天。事情哪有這么順利呢?結(jié)果又沒過多久,老板又收到客戶的投訴了,說自己明明更新了數(shù)據(jù),但是數(shù)據(jù)要過一段時間才生效,客戶接受不了。老板面無表情的找上阿旺,讓阿旺盡快查出問題。阿旺得知又有 Bug 就更慌了,立馬就登錄服務(wù)器去排查問題,查看日志后得知了原因?!赶雀聰?shù)據(jù)庫, 再刪除緩存」其實是兩個操作,前面的所有分析都是建立在這兩個操作都能同時執(zhí)行成功,而這次客戶投訴的問題就在于,刪除緩存(第二個操作)的時候失敗了,導(dǎo)致緩存中的數(shù)據(jù)是舊值。好在之前給緩存加上了過期時間,所以才會出現(xiàn)客戶說的過一段時間才更新生效的現(xiàn)象,假設(shè)如果沒有這個過期時間的兜底,那后續(xù)的請求讀到的就會一直是緩存中的舊數(shù)據(jù),這樣問題就更大了。所以新的問題來了,如何保證「先更新數(shù)據(jù)庫 ,再刪除緩存」這兩個操作能執(zhí)行成功?阿旺分析出問題后,慌慌張張的向老板匯報了問題。老板知道事情后,又給了阿旺幾天來解決這個問題,畫餅的事情這次沒有再提了。阿旺會用什么方式來解決這個問題呢?老板畫的餅事情,能否兌現(xiàn)給阿旺呢?預(yù)知后事,且聽下回阿旺的故事。
別問為什么,故事還要分上下回,因為小林昨晚熬夜寫,沒寫完,就先放上回的故事,哈哈哈。
3阿旺的事情就聊到這,我們繼續(xù)說點其他?!赶雀聰?shù)據(jù)庫,再刪除緩存」的方案雖然保證了數(shù)據(jù)庫與緩存的數(shù)據(jù)一致性,但是每次更新數(shù)據(jù)的時候,緩存的數(shù)據(jù)都會被刪除,這樣會對緩存的命中率帶來影響。所以,如果我們的業(yè)務(wù)對緩存命中率有很高的要求,我們可以采用「更新數(shù)據(jù)庫 更新緩存」的方案,因為更新緩存并不會出現(xiàn)緩存未命中的情況。但是這個方案前面我們也分析過,在兩個更新請求并發(fā)執(zhí)行的時候,會出現(xiàn)數(shù)據(jù)不一致的問題,因為更新數(shù)據(jù)庫和更新緩存這兩個操作是獨立的,而我們又沒有對操作做任何并發(fā)控制,那么當兩個線程并發(fā)更新它們的話,就會因為寫入順序的不同造成數(shù)據(jù)的不一致。所以我們得增加一些手段來解決這個問題,這里提供兩種做法:
  • 在更新緩存前先加個分布式鎖,保證同一時間只運行一個請求更新緩存,就會不會產(chǎn)生并發(fā)問題了,當然引入了鎖后,對于寫入的性能就會帶來影響。

  • 在更新完緩存時,給緩存加上較短的過期時間,這樣即時出現(xiàn)緩存不一致的情況,緩存的數(shù)據(jù)也會很快過期,對業(yè)務(wù)還是能接受的。

對了,針對「先刪除緩存,再刪除數(shù)據(jù)庫」方案在「讀 寫」并發(fā)請求而造成緩存不一致的解決辦法是「延遲雙刪」。延遲雙刪實現(xiàn)的偽代碼如下:#刪除緩存
redis.delKey(X)
#更新數(shù)據(jù)庫
db.update(X)
#睡眠
Thread.sleep(N)
#再刪除緩存
redis.delKey(X)
加了個睡眠時間,主要是為了確保請求 A 在睡眠的時候,請求 B 能夠在這這一段時間完成「從數(shù)據(jù)庫讀取數(shù)據(jù),再把缺失的緩存寫入緩存」的操作,然后請求 A 睡眠完,再刪除緩存。所以,請求 A 的睡眠時間就需要大于請求 B 「從數(shù)據(jù)庫讀取數(shù)據(jù) 寫入緩存」的時間。但是具體睡眠多久其實是個玄學(xué),很難評估出來,所以這個方案也只是盡可能保證一致性而已,極端情況下,依然也會出現(xiàn)緩存不一致的現(xiàn)象。因此,還是比較建議用「先更新數(shù)據(jù)庫,再刪除緩存」的方案。
本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉