【面試官】你可以說一下快取擊穿、穿透、雪崩的區別和解決方法嗎?

linux大本營發表於2020-12-19

資料獲取的流程,一般是前端請求,後臺先從快取中取資料,快取取不到則去資料庫中取,資料庫取到了則返回給前端,然後更新快取,如果資料庫取不到則返回空資料給前端

流程圖:
在這裡插入圖片描述

假如快取的資料沒有,後臺則會一直請求資料庫,對資料庫造成壓力,如果是請求量大或者惡意請求則會導致資料庫崩潰,我們一般稱為快取穿透、快取擊穿、快取雪崩。

1、快取穿透

描述:快取穿透是指快取和資料庫中都沒有的資料,而使用者不斷髮起請求,如發起為id為“-1”的資料或id為特別大(不存在的資料)。這時的使用者很可能是攻擊者,攻擊會導致資料庫壓力過大。
在這裡插入圖片描述

快取穿透

解決:

介面層增加校驗,如使用者鑑權校驗,id做基礎校驗,比如 id<=0的直接攔截;
從快取取不到的資料,在資料庫中也沒有取到,這時也可以將key-value對寫為key-null,直接返回空值。快取有效時間可以設定短點,如30秒(設定太長會導致正常情況也沒法使用)。這樣可以防止攻擊使用者反覆用同一個id暴力攻擊。
利用互斥鎖,快取失效的時候,先去獲得鎖,得到鎖了,再去請求資料庫。沒得到鎖,則休眠一段時間重試。
非同步更新。直接返回一個空值,然後啟動一個執行緒去資料庫讀資料,更新快取,比如專案啟動前先載入快取。
最常見的則是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被 這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。

【文章福利】需要C/C++ Linux伺服器架構師學習資料加群812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)

在這裡插入圖片描述

2、快取擊穿

描述:快取擊穿是指快取中沒有但資料庫中有的資料,當一個key非常熱點(類似於爆款),在不停的扛著大併發,大併發集中對這一個點進行訪問;當這個key在失效的瞬間,持續的大併發就穿破快取,直接請求資料庫,就像在一個屏障上鑿開了一個洞。
在這裡插入圖片描述

快取擊穿

解決:

設定熱點資料永遠不過期。
加互斥鎖。(或者分散式鎖)
如果快取中有資料,則直接返回,如果沒有,則第一個進入的執行緒先去查詢資料庫,並加上鎖,其他執行緒則等待,這樣就能防止去資料庫查重複資料、重複更新快取了。

3、快取雪崩

快取雪崩是指快取中資料大批量到過期時間,大批量資料同一時間過期,導致請求量全部請求到資料庫,造成資料庫當機。
在這裡插入圖片描述

快取雪崩

解決:

給快取失效時間,加上一個隨機值,避免大量快取集體失效。
雙快取:快取A和B,比如A的失效時間是20分鐘,B不失效。比如從A中沒讀到,就去B中讀,然後非同步起一個執行緒同步到A。

關於互斥鎖,可以看看下面這個例子:

Redis
如果是使用Redis,可以使用Redis的SETNX,也就是隻有不存在的時候才設定,可以利用它來實現鎖的效果。

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表快取值過期
          //設定3min的超時,防止del操作失敗的時候,下次快取過期一直不能load db
          if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表設定成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //這個時候代表同時候的其他執行緒已經load db並回設到快取了,這時候重試獲取快取值即可
                      sleep(50);
                      get(key);  //重試
              }
          } else {
              return value;      
          }
 }

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();  
    }  
} 

4、快取預熱

快取預熱就是系統上線後,後者系統在重啟的時候,將相關的快取資料直接載入到Redis。這樣就可以避免在使用者請求的時候,先查詢資料庫,然後再將資料快取的問題,使用者直接查詢事先被預熱的快取資料。

解決:

上線時加個介面,手動觸發載入快取,或者定時重新整理快取。
資料量不大,可以在專案啟動的時候自動進行載入。

關注公眾號,分享更多你感興趣的網際網路技術內容!在這裡插入圖片描述

相關文章