中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

緩存熱點key問題(mutex key)

發布時間:2020-09-14 12:17:54 來源:網絡 閱讀:640 作者:rr57e735816465a 欄目:開發技術

一、引出熱點key問題

 

       我們通常使用 緩存 + 過期時間的策略來幫助我們加速接口的訪問速度,減少了后端負載,同時保證功能的更新,一般情況下這種模式已經基本滿足要求了。

       但是有兩個問題如果同時出現,可能就會對系統造成致命的危害:

      (1) 這個key是一個熱點key(例如一個重要的新聞,一個熱門的八卦新聞等等),所以這種key訪問量可能非常大。

      (2) 緩存的構建是需要一定時間的。(可能是一個復雜計算,例如復雜的sql、多次IO、多個依賴(各種接口)等等

 

       于是就會出現一個致命問題:在緩存失效的瞬間,有大量線程來構建緩存(見下圖),造成后端負載加大,甚至可能會讓系統崩潰 。

 緩存熱點key問題(mutex key)

    

         

 

 

二、四種解決方案

 

我們的目標是:盡量少的線程構建緩存(甚至是一個) + 數據一致性 + 較少的潛在危險,下面會介紹四種方法來解決這個問題:

 

1. 使用互斥鎖(mutex key): 這種解決方案思路比較簡單,就是只讓一個線程構建緩存,其他線程等待構建緩存的線程執行完,重新從緩存獲取數據就可以了(如下圖)下載   

緩存熱點key問題(mutex key)

     如果是單機,可以用synchronized或者lock來處理,如果是分布式環境可以用分布式鎖就可以了(分布式鎖,可以用memcache的add, redis的setnx, zookeeper的添加節點操作)。

     下面是Tim yang博客的代碼,是memcache的偽代碼實現下載   

      

Java代碼  

  1. if (memcache.get(key) == null) {  

  2.     // 3 min timeout to avoid mutex holder crash  

  3.     if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  

  4.         value = db.get(key);  

  5.         memcache.set(key, value);  

  6.         memcache.delete(key_mutex);  

  7.     } else {  

  8.         sleep(50);  

  9.         retry();  

  10.     }  

  11. }  

     

 

      如果換成redis,就是:

Java代碼  下載   

  1. String get(String key) {  

  2.    String value = redis.get(key);  

  3.    if (value  == null) {  

  4.     if (redis.setnx(key_mutex, "1")) {  

  5.         // 3 min timeout to avoid mutex holder crash  

  6.         redis.expire(key_mutex, 3 * 60)  

  7.         value = db.get(key);  

  8.         redis.set(key, value);  

  9.         redis.delete(key_mutex);  

  10.     } else {  

  11.         //其他線程休息50毫秒后重試  

  12.         Thread.sleep(50);  

  13.         get(key);  

  14.     }  

  15.   }  

  16. }  

 

 

       

2. "提前"使用互斥鎖(mutex key):

   在value內部設置1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小。當從cache讀取到timeout1發現它已經過期時候,馬上延長timeout1并重新設置到cache。然后再從數據庫加載數據并設置到cache中。偽代碼如下:

 

Java代碼  下載   

  1. v = memcache.get(key);  

  2. if (v == null) {  

  3.     if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  

  4.         value = db.get(key);  

  5.         memcache.set(key, value);  

  6.         memcache.delete(key_mutex);  

  7.     } else {  

  8.         sleep(50);  

  9.         retry();  

  10.     }  

  11. else {  

  12.     if (v.timeout <= now()) {  

  13.         if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  

  14.             // extend the timeout for other threads  

  15.             v.timeout += 3 * 60 * 1000;  

  16.             memcache.set(key, v, KEY_TIMEOUT * 2);  

  17.   

  18.             // load the latest value from db  

  19.             v = db.get(key);  

  20.             v.timeout = KEY_TIMEOUT;  

  21.             memcache.set(key, value, KEY_TIMEOUT * 2);  

  22.             memcache.delete(key_mutex);  

  23.         } else {  

  24.             sleep(50);  

  25.             retry();  

  26.         }  

  27.     }  

  28. }  

 

 

 

3. "永遠不過期"

    

    這里的“永遠不過期”包含兩層意思:

    (1) 從redis上看,確實沒有設置過期時間,這就保證了,不會出現熱點key過期問題,也就是“物理”不過期。

    (2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value里,如果發現要過期了,通過一個后臺的異步線程進行緩存的構建,也就是“邏輯”過期下載   

   緩存熱點key問題(mutex key)

    從實戰看,這種方法對于性能非常友好,唯一不足的就是構建緩存時候,其余線程(非構建緩存的線程)可能訪問的是老數據,但是對于一般的互聯網功能來說這個還是可以忍受。

   

Java代碼  下載   

  1. String get(final String key) {  

  2.         V v = redis.get(key);  

  3.         String value = v.getValue();  

  4.         long timeout = v.getTimeout();  

  5.         if (v.timeout <= System.currentTimeMillis()) {  

  6.             // 異步更新后臺異常執行  

  7.             threadPool.execute(new Runnable() {  

  8.                 public void run() {  

  9.                     String keyMutex = "mutex:" + key;  

  10.                     if (redis.setnx(keyMutex, "1")) {  

  11.                         // 3 min timeout to avoid mutex holder crash  

  12.                         redis.expire(keyMutex, 3 * 60);  

  13.                         String dbValue = db.get(key);  

  14.                         redis.set(key, dbValue);  

  15.                         redis.delete(keyMutex);  

  16.                     }  

  17.                 }  

  18.             });  

  19.         }  

  20.         return value;  

  21.     }  

 

 

 

4. 資源保護

       可以做資源的隔離保護主線程池,如果把這個應用到緩存的構建也未嘗不可。下載   

緩存熱點key問題(mutex key)

 

 

 

三、四種方案對比:

 

      作為一個并發量較大的互聯網應用,我們的目標有3個:

      1. 加快用戶訪問速度,提高用戶體驗。

      2. 降低后端負載,保證系統平穩。下載   

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

      所以第二節中提到的四種方法,可以做如下比較,還是那就話:沒有最好,只有最合適。 

解決方案優點缺點
簡單分布式鎖(Tim yang)

 1. 思路簡單

2. 保證一致性

1. 代碼復雜度增大

2. 存在死鎖的風險

3. 存在線程池阻塞的風險

加另外一個過期時間(Tim yang) 1. 保證一致性同上 
不過期(本文)

1. 異步構建緩存,不會阻塞線程池

1. 不保證一致性。

2. 代碼復雜度增大(每個value都要維護一個timekey)。

3. 占用一定的內存空間(每個value都要維護一個timekey)。

資源隔離組件hystrix(本文)

1. hystrix技術成熟,有效保證后端。

2. hystrix監控強大。

 

 

1. 部分訪問存在降級策略。

 

 

四、總結

 

   1.  熱點key + 過期時間 + 復雜的構建緩存過程 => mutex key問題

   2. 構建緩存一個線程做就可以了。

   3. 四種解決方案:沒有最佳只有最合適。


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

类乌齐县| 五常市| 湘西| 江山市| 兴业县| 天峨县| 铜梁县| 望谟县| 盐亭县| 乐山市| 安庆市| 若尔盖县| 稻城县| 十堰市| 云林县| 沙坪坝区| 静海县| 丁青县| 紫云| 枣强县| 汨罗市| 白玉县| 彰化市| 宁河县| 通化县| 宜阳县| 柘荣县| 星子县| 淮安市| 和顺县| 潞城市| 和政县| 康乐县| 高安市| 河东区| 重庆市| 寿阳县| 云浮市| 翼城县| 万源市| 建平县|