<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>

          高并發(fā)下的緩存一致性,并發(fā),穿透問(wèn)題

          共 8645字,需瀏覽 18分鐘

           ·

          2021-04-06 09:23

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          76套java從入門到精通實(shí)戰(zhàn)課程分享

          緩存在高并發(fā)場(chǎng)景下的常見(jiàn)問(wèn)題

          緩存一致性問(wèn)題

          當(dāng)數(shù)據(jù)時(shí)效性要求很高的時(shí)候,需要保證緩存中的數(shù)據(jù)與數(shù)據(jù)庫(kù)中的保持一致,而且需要保證緩存節(jié)點(diǎn)和副本中的數(shù)據(jù)也要保持一致,不能出現(xiàn)差異現(xiàn)象。這樣就比較依賴緩存的過(guò)期和更新策略。一般會(huì)在數(shù)據(jù)庫(kù)發(fā)生更改的時(shí)候,主動(dòng)更新緩存中的數(shù)據(jù)或者移除對(duì)應(yīng)的緩存。

          1、更新數(shù)據(jù)庫(kù)成功—>更新緩存失敗—數(shù)據(jù)不一致

          2、更新緩存成功—>更新數(shù)據(jù)庫(kù)失敗—數(shù)據(jù)不一致

          3、更新數(shù)據(jù)庫(kù)成功—>淘汰緩存失敗—數(shù)據(jù)不一致

          4、淘汰緩存成功—>更新數(shù)據(jù)庫(kù)失敗—查詢緩存丟失


          緩存并發(fā)問(wèn)題

          緩存過(guò)期后將嘗試從數(shù)據(jù)庫(kù)獲取數(shù)據(jù),在單線程情況下是合理而又穩(wěn)固的流程,但是在高并發(fā)情況下,有可能多個(gè)請(qǐng)求并發(fā)的從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),對(duì)后端數(shù)據(jù)庫(kù)造成極大的壓力,甚至導(dǎo)致數(shù)據(jù)庫(kù)崩潰。另外,當(dāng)某個(gè)緩存的key被更新時(shí),同時(shí)也有可能在被大量的請(qǐng)求獲取,也同樣導(dǎo)致數(shù)據(jù)一致性的問(wèn)題。如何解決?一般我們會(huì)想到類似“鎖”的機(jī)制,在緩存更新或者過(guò)期的情況下,先獲取鎖,在進(jìn)行更新或者從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)后,再釋放鎖,需要一定的時(shí)間等待,就可以從緩存中繼續(xù)獲取數(shù)據(jù)

          • 使用互斥鎖(mutex key) 

          只讓一個(gè)線程構(gòu)建緩存,其他線程構(gòu)建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)就ojbk了 

          單機(jī)直接用synchronized或者lock,分布式就用分布式鎖(可以用memcache的add,redis的setnx,zookeeper的節(jié)點(diǎn)添加監(jiān)聽(tīng)等等等…..)


          memcache偽代碼如下

          if (memcache.get(key) == null) {  
              // 3 min timeout to avoid mutex holder crash  
              if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
                  value = db.get(key);  
                  memcache.set(key, value);  
                  memcache.delete(key_mutex);  
              } else {  
                  sleep(50);  
                  retry();  
              }  
          }  

          redis偽代碼

          String get(String key){
              String value = redis.get(key);
              if(value == null){
                  if(redis.setnx(key_Mutex),"1"){
                      redis.expire(key_mutex,3*60);//防止死鎖
                      value = db.get(key);
                      redis.set(key,value);
                      resdis.delete(key_Mutex);
                  }else{
                      Thread.sleep(50);
                      get(key);
                  }
              }
          }


          • 提前使用互斥鎖 

          在value內(nèi)部設(shè)置1個(gè)超時(shí)值(timeout1), timeout1比實(shí)際的memcache timeout(timeout2)小。當(dāng)從cache讀取到timeout1發(fā)現(xiàn)它已經(jīng)過(guò)期時(shí)候,馬上延長(zhǎng)timeout1并重新設(shè)置到cache。然后再?gòu)臄?shù)據(jù)庫(kù)加載數(shù)據(jù)并設(shè)置到cache中。偽代碼如下

          v = memcache.get(key);
          if (v == null) {
              if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
                  value = db.get(key);
                  memcache.set(key, value);
                  memcache.delete(key_mutex);
              } else {
                  sleep(50);
                  retry();
              }
          else {
              if (v.timeout <= now()) {
                  if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
                      // extend the timeout for other threads
                      v.timeout += 3 * 60 * 1000;
                      memcache.set(key, v, KEY_TIMEOUT * 2);

                      // load the latest value from db
                      v = db.get(key);
                      v.timeout = KEY_TIMEOUT;
                      memcache.set(key, value, KEY_TIMEOUT * 2);
                      memcache.delete(key_mutex);
                  } else {
                      sleep(50);
                      retry();
                  }
              }
          }


          上面兩種方案 

          優(yōu)點(diǎn):避免cache失效時(shí)刻大量請(qǐng)求獲取不到mutex并進(jìn)行sleep

          缺點(diǎn):代碼復(fù)雜性增大,會(huì)出現(xiàn)死鎖和線程池阻塞等問(wèn)題,因此一般場(chǎng)合用方案一也已經(jīng)足夠

          • 永遠(yuǎn)不過(guò)期 

          這里的“永遠(yuǎn)不過(guò)期”包含兩層意思: 

          (1) 從redis上看,沒(méi)有設(shè)置過(guò)期時(shí)間,就不會(huì)出現(xiàn)熱點(diǎn)key過(guò)期問(wèn)題,也就是“物理”不過(guò)期。 

          (2) 從功能上看,如果不過(guò)期,那不就成靜態(tài)的了嗎?所以我們把過(guò)期時(shí)間存在key對(duì)應(yīng)的value里,如果發(fā)現(xiàn)要過(guò)期了,通過(guò)一個(gè)后臺(tái)的異步線程進(jìn)行緩存的構(gòu)建,也就是“邏輯”過(guò)期,有一個(gè)問(wèn)題是在異步構(gòu)建緩存完成之前其他線程訪問(wèn)的是舊的數(shù)據(jù)

          String get(final String key) {  
                  V v = redis.get(key);  
                  String value = v.getValue();  
                  long timeout = v.getTimeout();  
                  if (v.timeout <= System.currentTimeMillis()) {  
                      // 異步更新后臺(tái)異常執(zhí)行  
                      threadPool.execute(new Runnable() {  
                          public void run() {  
                              String keyMutex = "mutex:" + key;  
                              if (redis.setnx(keyMutex, "1")) {  
                                  // 3 min timeout to avoid mutex holder crash  
                                  redis.expire(keyMutex, 3 * 60);  
                                  String dbValue = db.get(key);  
                                  redis.set(key, dbValue);  
                                  redis.delete(keyMutex);  
                              }  
                          }  
                      });  
                  }  
                  return value;  
              }  
          • 資源保護(hù)(尚未了解)


          緩存穿透問(wèn)題

          場(chǎng)景:在高并發(fā)場(chǎng)景下,如果一個(gè)key被高并發(fā)訪問(wèn),沒(méi)有被命中,處于對(duì)容錯(cuò)性的考慮,會(huì)嘗試去從后端數(shù)據(jù)庫(kù)中獲取,從而導(dǎo)致了大量請(qǐng)求到達(dá)數(shù)據(jù)庫(kù),而當(dāng)該key對(duì)應(yīng)的數(shù)據(jù)本身就是空的情況下,就導(dǎo)致數(shù)據(jù)庫(kù)中并發(fā)地去執(zhí)行很多不必要的查詢操作,從而導(dǎo)致巨大沖擊和壓力 

          可以通過(guò)下面的幾種常用方式來(lái)避免緩存問(wèn)題

          • 緩存空對(duì)象 

          對(duì)查詢結(jié)果為空的對(duì)象也進(jìn)行緩存,如果是集合,可以緩存一個(gè)空的集合(非null),如果是緩存單個(gè)對(duì)象,可以通過(guò)字段標(biāo)識(shí)來(lái)區(qū)分。這樣避免請(qǐng)求穿透到后端數(shù)據(jù)庫(kù),同時(shí),也需要保證緩存數(shù)據(jù)的時(shí)效性。適合命中不高,但可能被頻繁更新的數(shù)據(jù) 

          • 單獨(dú)過(guò)濾處理 

          對(duì)所有可能對(duì)應(yīng)數(shù)據(jù)為空的key進(jìn)行統(tǒng)一的存放,并在請(qǐng)求前做攔截,這樣避免請(qǐng)求穿透到后端數(shù)據(jù)庫(kù)。這種方式實(shí)現(xiàn)起來(lái)相對(duì)復(fù)雜,比較適合命中不高,但是更新不頻繁的數(shù)據(jù)

          總結(jié):作為一個(gè)并發(fā)量較大的互聯(lián)網(wǎng)應(yīng)用,我們的目標(biāo)有3個(gè):

            1. 加快用戶訪問(wèn)速度,提高用戶體驗(yàn)。

            2. 降低后端負(fù)載,保證系統(tǒng)平穩(wěn)。

            3. 保證數(shù)據(jù)“盡可能”及時(shí)更新(要不要完全一致,取決于業(yè)務(wù),而不是技術(shù)。)

          ---接下來(lái)一篇將對(duì) 緩存雪崩 做個(gè)簡(jiǎn)單的總結(jié)

          ————————————————

          版權(quán)聲明:本文為CSDN博主「Kevins Danish」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

          原文鏈接:

          https://blog.csdn.net/weixin_36708538/article/details/80338643




          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長(zhǎng)按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 

          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  欧美天天性| 日韩黄片在线看 | 日韩一区二区三区四区久久久精品有吗 | 伊人色色网 | 大香蕉亚洲网 |