redis的緩存雪崩,穿透,擊穿介紹和如何解決(四)


Redis緩存雪崩

什麼是緩存雪崩?

雪崩,雪一樣的大面積崩。一些緩存設定用的是定時更新,這樣的數據緩存是同時刷新,同時失效。比如失效時剛好跨零點跨天,商品開始搶購,大量請求進來,Redis緩存失效,只能全部打到DB去了,如果是小量請求,沒慢日誌,分庫分表的數據庫倒還扛得住,這時如果大量的話,1秒幾千個請求進來,數據庫必定掛了,再重啓,再掛,這就是緩存雪崩

雪崩解決方法?

  1. 在批量設置Redis緩存時候,把每個Key的失效時間加上個隨機值,time() + rand(1, 1000),保證不會都在同一時間失效
  2. 熱點數據直接設置爲不過期,數據有更新時直接刷新下緩存
  3. 用Redis集羣部署,把數據分佈在不同的Redis分片中,也可避免全部失效直打DB問題

Redis緩存穿透

什麼是緩存穿透?

穿透,穿透過已有的緩存層,直接到DB。訪問的數據在Redis緩存中沒有,就去DB,DB也沒有,沒有設值到緩存,相當於每個請求都是透明的直接請求DB。比如請求id爲負數,而用戶不斷髮起請求,如果是黑客攻擊,數據庫壓力就會很大,嚴重時數據庫就會跨掉

穿透解決方法?

  1. 請求參數做基準校驗,只要是請求的就要驗證過濾,不信任任何方的請求參數,不符合就return

  2. 在接入層,通過nginx用limit_req_zone和limit_req_conn限制單個ip的每秒訪問速率和併發數

  3. 讀取DB中沒有,也要設置key緩存爲空值(Null),再加上一個短時的過期時間,比如30秒,讓其可自動剔除。加短時間超時,一方面是爲了防止請求大量不存在的key,內存會佔用越來越大,消耗內存;一方面是爲了防止正常情況下空值導致無法使用

  4. 用布隆過濾器(BloomFilter),簡單介紹就是把有的key都放到一個大的集合裏,它是一個二進制向量,存放的不是0就是1,當key檢測不存在集合中,則是非法參數,直接return

    (1) 加入key到集合時,需要把key通過多個不同的算法計算對應位數,比如 JSHash算法,算出對應位數X,則設X爲1

    (2) 檢測是否在集合中,則通過算法獲取對應的位數下值是否都爲1 ,只要有一個爲 0 則表示不存在集合中。所以,缺點就是集合越大,有可能出現誤判,存在判爲不存在

    (3) 例子:
    比如新建一個長度爲8的布隆過濾器,一開始集合爲空的時候,默認值都是0

    0 0 0 0 0 0 0 0

    加入一個key,通過Hash1算法 Hash1(key)= 2,Hash2(key) = 4,Hash3(key) = 7,把下標2,下標4,下標7標 注爲1

    0 0 1 0 1 0 0 1

    驗證時,則把key通過三個算法獲取對應獲取下標2,4,7,判斷是否都爲1,則key存在集合中

    (4) 主要直觀代碼:

    $keyBloom = "bloom:filter"; //集合名,總的key
    //加入布隆集合
    function add($item) {
       $hash1Pos = Hash1($item); 
    	$hash2Pos = Hash2($item);
       $hash3Pos = Hash3($item);
    	//用管道,合併一次IO
       $pipe = $redis->pipeline();
       $pipe->setbit($keyBloom, $hash1Pos, 1);
       $pipe->setbit($keyBloom, $hash2Pos, 1);
       $pipe->setbit($keyBloom, $hash3Pos, 1);
       $pipe->execute();
    }
    //判斷是否存在集合中
    //return true | false
    public function has($item) {
       $hash1Pos = Hash1($item); 
       $hash2Pos = Hash2($item);
       $hash3Pos = Hash3($item);
    	//用管道,合併一次IO
       $pipe = $redis->pipeline();
       $pipe->getbit($keyBloom, $hash1Pos);
       $pipe->getbit($keyBloom, $hash2Pos);
       $pipe->getbit($keyBloom, $hash3Pos);
       $result = $pipe->execute();
       return !in_array(0, $result);
    }
    

什麼是Redis緩存擊穿?

擊穿則是跟緩存雪崩有點類似,只不過雪崩是大面積,擊穿是單個key非常熱點,一直扛着高併發請求,當key在失效瞬間,持續的高併發請求直打DB,DB壓力急劇上升

擊穿解決方法?

  1. 既然是這麼高熱點數據,設置爲不過期,有更新刷新下緩存

  2. 延時雙重檢測,首次獲取緩存爲空,先睡一小會,再獲取緩存檢測,還是爲空再讀DB設值到緩存,主要代碼如下

    $result = $redis->get("testKey");
    //首次檢測
    if(!isset($result)){
        //睡0.3秒,再檢測
        usleep(3000);
        $result = $redis->get("testKey");
        if(!isset($result)){
           //還是沒有,讀db,設值到緩存
           $result = GetDataByDB("testKey");
           $redis->set("testKey", $result);
        }
    }
    return $result;
    
  3. 互斥鎖,獲取緩存沒有,先獲取操作鎖,取得鎖之後,讀取DB設值到緩存

    互斥鎖操作主要代碼如下

    $now = time();
    //判斷和設置時候要有原子性,用eval+lua實現,防止併發,髒讀情況
    //設置鎖爲10秒自動失效
    $lua = <<<eof
        local key = KEYS[1]
        local val = KEYS[2]
        local lock = redis.call('get', key)
        if lock then
            if (tonumber(lock) > tonumber(val)) then
                return false
            end
            redis.call('set', key, val + 10)
            return true
        else
            redis.call('set', key, val + 10)
            return true
        end
    eof;
    $arr = ["lock", $now];
    //獲取操作鎖
    $result = $redis->eval($lua, $arr, count($arr));
    if ($result) {
        //
        //查詢數據庫然後把值同步到緩存邏輯~~~~~~
        //
        //移除鎖
        $redis->del("lock");
        echo "讀db完成";
    }
    else{
        echo "互斥中";
    }
    

簡單總結:

  1. Qps高的,可用Redis集羣,主從, 哨兵實現高可用,避免全盤崩潰

  2. 請求接入層限流,比如nginx的ip,連接數限制,超出閥值拉黑

  3. 採用AOF做熱備,RDB做冷備,持久化保存,實現服務器穩健

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章