Redis緩存雪崩,擊穿,穿透

1. Redis緩存雪崩redis

發生場景數據庫

當Redis服務器重啓或者大量緩存在同一時期失效時,此時大量的流量會所有衝擊到數據庫上面,數據庫有可能會由於承受不住而宕機;
因此此時的緩存層出現了錯誤,因而全部的請求都會到達存儲層,存儲層的調用量會暴漲,形成存儲層也完蛋了的狀況;緩存

解決方案tomcat

  • 均勻分佈 : 咱們應該在設置失效時間時應該儘可能均勻的分佈,好比失效時間是當前時間加上一個時間段的隨機值,讓失效的時間保持隨機性,不在同一時間點失效。
  • 熔斷機制 : 相似於SpringCloud的熔斷器,咱們能夠設定閾值或監控服務,若是達到熔斷閾值(QPS,服務沒法響應,服務超時)時,則直接返回,再也不調用目標服務,而且還須要一個檢測機制,若是目標服務已經能夠正常使用,則重置閾值,恢復使用。
  • 隔離機制 : 相似於Docker同樣,當一個服務器上某一個tomcat出了問題後不會影響到其它的tomcat,這裏咱們可使用線程池來達到隔離的目的,當線程池執行拒絕策略後則直接返回,再也不向線程池中增長任務。
  • 限流/降流機制 : 其實限流就是熔斷機制的一個版本,設置閾值(QPS),達到閾值以後直接返回。
  • 雙緩存機制 : 將數據存儲到緩存中時存儲倆份,一份的有效期是正常的,一份的有效期長一點.不建議用這個方案,由於比較消耗內存資源,畢竟Redis是直接存儲到內存中的。
  • **集羣/redis高可用:**既然一臺redis扛不住,那就多弄幾臺,這臺掛掉還有其它的能夠繼續工做,也就是搭建的一個集羣。

2. Redis緩存穿透服務器

發生場景併發

當用戶想查詢某條數據,發現redis數據庫沒有,即緩存沒有命中;繼續向持久層數據庫查詢,仍是沒有,即本次查詢失敗;當不少次用戶查詢都失敗,因而請求都去請求 了持久層;此時持久層的數據庫就會產生很大很大壓力,因而就出現了緩存穿透。ide

解決方案高併發

布隆過濾 : 咱們能夠預先將數據庫裏面全部的key所有存到一個大的map裏面,而後在過濾器中過濾掉那些不存在的key.可是須要考慮數據庫的key是會更新的,此時須要考慮數據庫 --> map的更新頻率問題
緩存空值 : 哪怕這條數據不存在可是咱們任然將其存儲到緩存中去,設置一個較短的過時時間便可,而且能夠作日誌記錄,尋找問題緣由工具

3. Redis緩存擊穿url

緩存擊穿,是指一個key很是熱點,在不停的扛着大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破緩存,直接請求數據庫,就像在一個屏障上鑿開了一個洞。
好比當前的商品降價,促銷;或者微博熱搜不少人同時訪問,那麼這個Key就是一個熱點,同時扛着超高併發,集中訪問,緩存就被擊穿。

解決方案

使用互斥鎖(mutex key)

業界比較經常使用的作法,是使用mutex。簡單地來講,就是在緩存失效的時候(判斷拿出來的值爲空),不是當即去load db,而是先使用緩存工具的某些帶成功操做返回值的操做(好比Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操做返回成功時,再進行load db的操做並回設緩存;不然,就重試整個get緩存的方法。
SETNX,是「SET if Not eXists」的縮寫,也就是隻有不存在的時候才設置,能夠利用它來實現鎖的效果。在redis2.6.1以前版本未實現setnx的過時時間,因此這裏給出兩種版本代碼參考:

//2.6.1前單機版本鎖
String get(String key) {  
   String value = redis.get(key);  
   if (value  == null) {  
    if (redis.setnx(key_mutex, "1")) {  
        // 3 min timeout to avoid mutex holder crash  
        redis.expire(key_mutex, 3 * 60)  
        value = db.get(key);  
        redis.set(key, value);  
        redis.delete(key_mutex);  
    } else {  
        //其餘線程休息50毫秒後重試  
        Thread.sleep(50);  
        get(key);  
    }  
  }  
}
最新版本代碼:
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;      
          }
 }
mem

文獻參考:
https://baijiahao.baidu.com/sid=1655304940308056733&wfr=spider&for=pc
https://blog.csdn.net/zeb_perfect/article/details/54135506

相關文章
相關標籤/搜索