<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          美團面試題:緩存一致性,我是這么回答的!

          共 3002字,需瀏覽 7分鐘

           ·

          2021-05-28 14:49

          人生本就是苦還是只有童年苦?生命就是如此!

          -----這個殺手不太冷

          前言

          一道之前的面試題:

          如何保證緩存和數(shù)據(jù)庫的一致性?

          來自:社招一年半面經(jīng)分享(含阿里美團頭條京東滴滴)

          文章首發(fā)在公眾號,之后同步掘金和個人網(wǎng)站:https://upheart.cn/?。?/p>

          下面介紹幾種方案(大家回答的時候最好根據(jù)自己的業(yè)務,結(jié)合下面的方案)

          文章較長,可以點贊在看

          方案分析

          更新緩存策略方式常見的有下面幾種:

          1. 先更新緩存,再更新數(shù)據(jù)庫
          2. 先更新數(shù)據(jù)庫,再更新緩存
          3. 先刪除緩存,再更新數(shù)據(jù)庫
          4. 先更新數(shù)據(jù)庫,再刪除緩存

          下面一一介紹!

          方案一:更新緩存,更新數(shù)據(jù)庫

          這種方式可輕易排除,因為如果先更新緩存成功,但是數(shù)據(jù)庫更新失敗,則肯定會造成數(shù)據(jù)不一致。

          方案二:更新數(shù)據(jù)庫,更新緩存

          這種緩存更新策略俗稱雙寫,存在問題是:并發(fā)更新數(shù)據(jù)庫場景下,會將臟數(shù)據(jù)刷到緩存

          updateDB();
          updateRedis();

          舉例:如果在兩個操作之間數(shù)據(jù)庫和緩存又被后面請求修改,此時再去更新緩存已經(jīng)是過期數(shù)據(jù)了。

          方案三:刪除緩存,更新數(shù)據(jù)庫

          存在問題:更新數(shù)據(jù)庫之前,若有查詢請求,會將臟數(shù)據(jù)刷到緩存

          deleteRedis();
          updateDB();

          舉例:如果在兩個操作之間發(fā)生了數(shù)據(jù)查詢,那么會有舊數(shù)據(jù)放入緩存。

          該方案會導致請求數(shù)據(jù)不一致

          如果同時有一個請求A進行更新操作,另一個請求B進行查詢操作。那么會出現(xiàn)如下情形:

          • 請求A進行寫操作,刪除緩存

          • 請求B查詢發(fā)現(xiàn)緩存不存在

          • 請求B去數(shù)據(jù)庫查詢得到舊值

          • 請求B將舊值寫入緩存

          • 請求A將新值寫入數(shù)據(jù)庫

          上述情況就會導致不一致的情形出現(xiàn)。而且,如果不采用給緩存設置過期時間策略,該數(shù)據(jù)永遠都是臟數(shù)據(jù)。

          方案四:更新數(shù)據(jù)庫,刪除緩存

          存在問題:在更新數(shù)據(jù)庫之前有查詢請求,并且緩存失效了,會查詢數(shù)據(jù)庫,然后更新緩存。如果在查詢數(shù)據(jù)庫和更新緩存之間進行了數(shù)據(jù)庫更新的操作,那么就會把臟數(shù)據(jù)刷到緩存

          updateDB();
          deleteRedis();

          舉例:如果在查詢數(shù)據(jù)庫和放入緩存這兩個操作中間發(fā)生了數(shù)據(jù)更新并且刪除緩存,那么會有舊數(shù)據(jù)放入緩存。

          假設有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那么會有如下情形產(chǎn)生

          • 緩存剛好失效

          • 請求A查詢數(shù)據(jù)庫,得一個舊值

          • 請求B將新值寫入數(shù)據(jù)庫

          • 請求B刪除緩存

          • 請求A將查到的舊值寫入緩存

          如果發(fā)生上述情況,確實是會發(fā)生臟數(shù)據(jù)。但是發(fā)生上述情況有一個先天性條件,就是寫數(shù)據(jù)庫操作比讀數(shù)據(jù)庫操作耗時更短

          不過數(shù)據(jù)庫的讀操作的速度遠快于寫操作的

          因此這一情形很難出現(xiàn)。

          方案對比

          方案1和方案2的共同缺點:

          并發(fā)更新數(shù)據(jù)庫場景下,會將臟數(shù)據(jù)刷到緩存,但一般并發(fā)寫的場景概率都相對小一些;

          線程安全角度,會產(chǎn)生臟數(shù)據(jù),比如:

          • 線程A更新了數(shù)據(jù)庫
          • 線程B更新了數(shù)據(jù)庫
          • 線程B更新了緩存
          • 線程A更新了緩存

          方案3和方案4的共同缺點:

          不管采用哪種順序,2種方式都是存在一些問題的:

          • 主從延時問題:不管是先刪除還是后刪除,數(shù)據(jù)庫主從延時可能導致臟數(shù)據(jù)的產(chǎn)生。
          • 緩存刪除失?。喝绻彺鎰h除失敗,則都會產(chǎn)生臟數(shù)據(jù)。

          問題解決思路:延遲雙刪,添加重試機制,下面介紹!

          更新緩存還是刪除緩存?

          1.更新緩存緩存需要有一定的維護成本,而且會存在并發(fā)更新的問題

          2.寫多讀少的情況下,讀請求還沒有來,緩存以及被更新很多次,沒有起到緩存的作用

          3.放入緩存的值可能是經(jīng)過復雜計算的,如果每次更新,都計算寫入緩存的值,浪費性能的

          刪除緩存優(yōu)點:簡單、成本低,容易開發(fā);缺點:會造成一次cache miss

          如果更新緩存開銷較小并且讀多寫少,基本不會有寫并發(fā)的時候可以才用更新緩存,否則通用做法還是刪除緩存。

          總結(jié)

          方案問題問題出現(xiàn)概率推薦程度
          更新緩存 -> 更新數(shù)據(jù)庫為了保證數(shù)據(jù)準確性,數(shù)據(jù)必須以數(shù)據(jù)庫更新結(jié)果為準,所以該方案絕不可行不推薦
          更新數(shù)據(jù)庫 -> 更新緩存并發(fā)更新數(shù)據(jù)庫場景下,會將臟數(shù)據(jù)刷到緩存并發(fā)寫場景,概率一般寫請求較多時會出現(xiàn)不一致問題,不推薦使用。
          刪除緩存 -> 更新數(shù)據(jù)庫更新數(shù)據(jù)庫之前,若有查詢請求,會將臟數(shù)據(jù)刷到緩存并發(fā)讀場景,概率較大讀請求較多時會出現(xiàn)不一致問題,不推薦使用
          更新數(shù)據(jù)庫 -> 刪除緩存在更新數(shù)據(jù)庫之前有查詢請求,并且緩存失效了,會查詢數(shù)據(jù)庫,然后更新緩存。如果在查詢數(shù)據(jù)庫和更新緩存之間進行了數(shù)據(jù)庫更新的操作,那么就會把臟數(shù)據(jù)刷到緩存并發(fā)讀場景&讀操作慢于寫操作,概率最小讀操作比寫操作更慢的情況較少,相比于其他方式出錯的概率小一些。勉強推薦。

          推薦方案

          延遲雙刪

          采用更新前后雙刪除緩存策略

          public void write(String key,Object data){
            redis.del(key);
               db.update(data);
               Thread.sleep(1000);
               redis.del(key);
           }
          • 先淘汰緩存

          • 再寫數(shù)據(jù)庫

          • 休眠1秒,再次淘汰緩存

          大家應該評估自己的項目的讀數(shù)據(jù)業(yè)務邏輯的耗時。然后寫數(shù)據(jù)的休眠時間則在讀數(shù)據(jù)業(yè)務邏輯的耗時基礎上即可。

          這么做的目的,就是確保讀請求結(jié)束,寫請求可以刪除讀請求造成的緩存臟數(shù)據(jù)。

          問題及解法:

          1、同步刪除,吞吐量降低如何處理

          將第二次刪除作為異步的,提交一個延遲的執(zhí)行任務

          2、解決刪除失敗的方式:

          添加重試機制,例如:將刪除失敗的key,寫入消息隊列;但對業(yè)務耦合有些嚴重;

          延時工具可以選擇:

          最普通的阻塞Thread.currentThread().sleep(1000);

          Jdk調(diào)度線程池,quartz定時任務,利用jdk自帶的delayQueue,netty的HashWheelTimer,Rabbitmq的延時隊列,等等

          實際場景

          我們有個商品中心的場景,是讀多寫少的服務,并且寫數(shù)據(jù)會發(fā)送MQ通知下游拿數(shù)據(jù),這樣就需要嚴格保證緩存和數(shù)據(jù)庫的一致性,需要提供高可靠的系統(tǒng)服務能力。

          寫緩存策略

          1. 緩存key設置失效時間
          2. 先DB操作,再緩存失效
          3. 寫操作都標記key(美團中間件)強制走主庫
          4. 接入美團中間件監(jiān)聽binlog(美團中間件)變化的數(shù)據(jù)在進行兜底,再刪除緩存

          讀緩存策略

          1. 先判斷是否走主庫
          2. 如果走主庫,則使用標記(美團中間件)查主庫
          3. 如果不是,則查看緩存中是否有數(shù)據(jù)
          4. 緩存中有數(shù)據(jù),則使用緩存數(shù)據(jù)作為結(jié)果
          5. 如果沒有,則查DB數(shù)據(jù),再寫數(shù)據(jù)到緩存

          注意

          關于緩存過期時間的問題

          如果緩存設置了過期時間,那么上述的所有不一致情況都只是暫時的。

          但是如果沒有設置過期時間,那么不一致問題就只能等到下次更新數(shù)據(jù)時解決。

          所以一定要設置緩存過期時間

          最后

          覺得有收獲,希望幫忙點贊,轉(zhuǎn)發(fā)下哈,謝謝,謝謝

          微信搜索:月伴飛魚,交個朋友

          公眾號后臺回復666,可以獲得免費電子書籍

          動態(tài)代理總結(jié),面試你要知道的都在這里,無廢話!


          這些線程安全的坑,你在工作中踩了么?


          面試題:Kafka如何保證高可用?有圖有真相

          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品秘 一区二区三区蜜桃久 | 国产精品视频免费在线观看 | 久久夜色精品 | 精品婷婷| 26∪u∪成人网站 |