Redis緩存雪崩
什麼是緩存雪崩?
雪崩,雪一樣的大面積崩。一些緩存設定用的是定時更新,這樣的數據緩存是同時刷新,同時失效。比如失效時剛好跨零點跨天,商品開始搶購,大量請求進來,Redis緩存失效,只能全部打到DB去了,如果是小量請求,沒慢日誌,分庫分表的數據庫倒還扛得住,這時如果大量的話,1秒幾千個請求進來,數據庫必定掛了,再重啓,再掛,這就是緩存雪崩
雪崩解決方法?
- 在批量設置Redis緩存時候,把每個Key的失效時間加上個隨機值,time() + rand(1, 1000),保證不會都在同一時間失效
- 熱點數據直接設置爲不過期,數據有更新時直接刷新下緩存
- 用Redis集羣部署,把數據分佈在不同的Redis分片中,也可避免全部失效直打DB問題
Redis緩存穿透
什麼是緩存穿透?
穿透,穿透過已有的緩存層,直接到DB。訪問的數據在Redis緩存中沒有,就去DB,DB也沒有,沒有設值到緩存,相當於每個請求都是透明的直接請求DB。比如請求id爲負數,而用戶不斷髮起請求,如果是黑客攻擊,數據庫壓力就會很大,嚴重時數據庫就會跨掉
穿透解決方法?
-
請求參數做基準校驗,只要是請求的就要驗證過濾,不信任任何方的請求參數,不符合就return
-
在接入層,通過nginx用limit_req_zone和limit_req_conn限制單個ip的每秒訪問速率和併發數
-
讀取DB中沒有,也要設置key緩存爲空值(Null),再加上一個短時的過期時間,比如30秒,讓其可自動剔除。加短時間超時,一方面是爲了防止請求大量不存在的key,內存會佔用越來越大,消耗內存;一方面是爲了防止正常情況下空值導致無法使用
-
用布隆過濾器(BloomFilter),簡單介紹就是把有的key都放到一個大的集合裏,它是一個二進制向量,存放的不是0就是1,當key檢測不存在集合中,則是非法參數,直接return
(1) 加入key到集合時,需要把key通過多個不同的算法計算對應位數,比如 JSHash算法,算出對應位數X,則設X爲1
(2) 檢測是否在集合中,則通過算法獲取對應的位數下值是否都爲1 ,只要有一個爲 0 則表示不存在集合中。所以,缺點就是集合越大,有可能出現誤判,存在判爲不存在
(3) 例子:
比如新建一個長度爲8的布隆過濾器,一開始集合爲空的時候,默認值都是00 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壓力急劇上升
擊穿解決方法?
-
既然是這麼高熱點數據,設置爲不過期,有更新刷新下緩存
-
延時雙重檢測,首次獲取緩存爲空,先睡一小會,再獲取緩存檢測,還是爲空再讀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;
-
互斥鎖,獲取緩存沒有,先獲取操作鎖,取得鎖之後,讀取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 "互斥中"; }
簡單總結:
-
Qps高的,可用Redis集羣,主從, 哨兵實現高可用,避免全盤崩潰
-
請求接入層限流,比如nginx的ip,連接數限制,超出閥值拉黑
-
採用AOF做熱備,RDB做冷備,持久化保存,實現服務器穩健