前面的文章我們主要聊了一些redis 的基礎知識,一直沒有實戰或實際中遇到的問題,大家會枯燥無味些,今天我就來聊聊實戰。
相信這三個問題,網路上已經有很多的夥伴講過了,但是今天我還是想說下,會多畫圖,讓大家加深印象,這三個問題也高頻的面試題,但是能把這幾個問題說清楚,也是需要技巧的。 【相關推薦:Redis影片教學】
再說這三個問題的時候,先說下正常的請求流程,看圖片說話:
上圖的意思大致如下:
首先會在你的程式碼中,可能是tomcat 也可以是你的rpc 服務中,先判斷快取cache 中是否存在你想要的數據,如果儲存了,那麼直接傳回給呼叫端,如果不存在,那麼就需要查詢資料庫,查詢出結果來,再繼續快取到cache中,然後傳回結果給呼叫方,下次再來的查詢的時候,也就命中緩存了。
定義
#記得之前在做推薦系統的時候,有些資料是離線演算法算出來的,需求是看了這個商品會推薦哪些相似的商品,這個算出來之後會存儲到hbase,同時存儲到redis,由於都是批量算法出來的,再存儲到redis 的時候,如果過期時間設置相同,那麼就會造成大批量的key ,在同一時刻失效,那麼就會有大批量的請求會被打到後台的數據庫上,因為數據庫的吞吐量是有限的,很有可能會把數據庫打垮的,這種情況就是快取雪崩,看圖說話:
這個主要是說明一個快取雪崩出現的場景,尤其是定時任務在批次設定cache的時候,一定要注意過期時間的設定。
如何預防雪崩
其實也很簡單,就是在你批次設定cache的快取時間的時候,給設定的快取時間,設定一個隨機數(如隨機數可以10分鐘內的數字,隨機數的生成可以用java的Random生成),這樣,就不會出現大量的key,再同一時刻集體失效了,看圖說話:
如果真的發生了雪崩怎麼辦?
流量不太大,資料庫能抗住,ok,恭喜你逃過一劫。
流量很大,超過了資料庫所能處理的請求數的極限,資料庫down機了,也恭喜你領了一個P0事故單。
流量很大,如果你的資料庫有限流方案,當達到了限流設定的參數,那麼就會拒絕請求,從而保護了後台db。這裡對限流多說幾句。
可以透過設定每秒請求數,來限制大量的請求到達db端,注意這裡的每秒請求數,或者說是並發數,並不是資料當前的每秒請求數,可以設定為查詢某個key 對應的每秒請求數量,這樣做的目的,是防止大量相同key的請求到達後端資料庫,這樣就能攔截了大部分請求了。
看圖說話:
這樣相同的key,就會被限流了大部分請求,從而保護了資料庫db。
其實限流也分為本地限流和分散式限流兩種,後面的文章裡,我會 介紹本地限流和redis 實現的分散式限流。
定義
#例如在某網站在進行雙十一或在搞秒殺等營運活動的時候,那麼此時網站流量一般都會很大的,某個一個商品因為促銷會成為爆品,流量超級的大,如果這個商品,在這個時候,由於某種原因,在cache內失效了,那麼就瞬間這個key的流量都會湧向資料庫了,那麼db最後撐不住了,down了,後果可想而知啊,正常其他的資料也查詢不了。
看圖片說話:
redis 中的huawei pro 這個key 突然失效了,可能是到期了,可能是記憶體不夠被淘汰了,那麼就會有大流量的請求到達redis ,發現redis 沒有這個key,那麼這些流量,就會轉到DB 上去,查詢對應的huawei pro,此時DB 挺不住了,down了。
如何解決
其實歸根到底還是不能讓更多的流量到達DB就行了,所以我們就是要限製到達db的流量就可以了。
1、限流
和上面說的類似,主要是限制某個key的流量,當這個key ,被擊穿後,限制只有一個流量進入到db,其他都被拒絕,或等待重試查詢redis。
限流的圖可以參考快取擊穿限流的圖。
這裡也會分本地限流和分散式限流 。
何為本地限流,就是在本地單一實例範圍內,限制這個key的流量多少,只對目前實例有效。
何為分散式限流呢,就是在分散式的環境下,多個實例的範圍內,這個key的限制流量的累加是來自多個實例的流量,達到限制,所有的實例都會限制流量到達DB。
2、利用分散式鎖定
這裡簡單說下分散式鎖定的定義,在並發場景下,需要使用鎖定對共享資源互斥存取來保證線程安全;同樣,在分散式場景下,也需要一種機制來確保對多節點共享資源的互斥訪問,實現機制就是分散式鎖。
在這裡共享資源就是例子中的huawei pro,也就是在訪問db中的huawei pro 的時候,要確保只有一個線程或者一個流量去訪問,就達到了分佈式鎖的效果。
看圖片說話:
去搶鎖:
#大量請求在沒有取得到huawei pro 這個key的值後,準備去db取得數據,此時取得db的程式碼加了分散式鎖,那麼每個請求,也是每個執行緒都會去取得huawei pro 的分散式鎖(圖中利用redis實作了分散式鎖,後面我會有單獨一篇文章來介紹分散式鎖的實現,不限於redis)。
取得鎖定之後:
此時執行緒A取得了huawei pro 的分散式鎖,那麼執行緒A就會去DB載入數據,然後由線程A將huawei pro 再次設定到cache內,然後返回資料。
其他的執行緒就沒有取得到,一種方式就是直接傳回空值給客戶端,還有一個等待50-100ms ,因為查詢db和放入redis 會很快,此時等待,再一次查詢的時候,結果可能就有了,如果沒有就直接回傳null,當然也可以重試,當然在大並發的場景下,還是希望能夠快速的回傳結果,不能發生太多次數的重試操作。
3、定時任務更新熱點key
這個就很好理解,說穿了,就是一個定時任務定時的去監控某些熱點key的超時時間,是否到期,再進行快到期了的時候延長key在cache中的快取時間就可以了。
單一執行緒輪詢的方式檢查和更新失效時間,看圖:
多執行緒的方式,注意熱點的key 不能太多,某個執行緒會開啟很多,如果熱點key很多,可以採用執行緒池的方式,看圖:
延遲隊列實現
上面的方式說白了,無論是單一線程還是多個線程,都是會採用輪詢的方式(每次白白浪費的cpu),來檢查是否key 快到期了,這種方式檢查會存在檢查時間不准確,可能會造成時間的延遲或者不准確,你在等待進行下次檢查的時候,這個key就沒了,那麼此時就已經發了擊穿,這個情況的發生雖然機率低,但也是有的,那麼我們怎麼才能避免呢,其實咱們可以利用延遲隊列(環形隊列來實現,這裡我不深入講這個隊列的原理了,大家可以自行百度或者google ),所謂的延遲隊列就是你往這個隊列發送訊息,希望按照你設定的時間來進行消費,時間沒到不會進行消費,時間到了就進行消費,好了,看圖說話吧:
1、程式首次啟動取得名單內key的失效時間。
2、依序設定key 延遲消費的時間,注意這個消費時間要比失效時間早。
3、延遲佇列到期,消費端進行消費key。
4、消費端消費訊息,延遲key的失效時間到cache。
5、再次發送key 新的失效時間到延遲隊列,等待下次延遲cache的失效時間。
4、設定key 不失效
這種其實也可能會因為記憶體不足,key 被淘汰,大家可以想想什麼情況下,key 會被淘汰。
定義
所謂穿透,就是存取了一個cache不存在,資料庫裡也不存在的key ,那麼此時相當於流量直接到達了DB 了,那麼一些流氓就可以利用這個漏洞,瘋狂的刷你的接口,進而把你的DB打垮,你的業務也就不能正常運行了。
如何解決呢?
1、設定null 或特殊值
我們可以透過設定null 或特定的值到redis內,且不過期,那麼下次再來的時候,直接從redis 取得這個null 或者特殊值就可以了。
這個方案不能解決根本性的問題,如果這個流量能仿造出大量的無用key,你設定再多的null或特殊的值都是沒有用的,那我們該怎麼解決呢?
2、布隆過濾器
布隆過濾器英文為bloomfiler,這裡我們只是做簡單的介紹,介於篇幅的原因,後面會有單獨的文章做介紹。
舉個例子,如果我們資料庫裡儲存著千萬級的sku 數據,我們現在的需求是如果庫有這個sku,那麼就查詢redis ,如果redis 沒有就查詢資料庫,然後更新redis,我們最先想到的就是把sku資料放入到一hashmap內,key 就是sku,因為sku 的數量很多,那麼這個hashmap佔用的內存空間會很大,有可能會撐爆內存,最後得不償失了,那麼怎麼來節省內存,我們可以利用一個bit的數組,來儲存這個sku是否存在狀態,0 代表不存在,1 代表存在,我們可以利用一個雜湊函數,算出sku的雜湊值,然後sku的雜湊值對bit數組進行取模,找到所在數組的位置,然後設定為1,當請求來的時候,我們會算出這個sku 散列值對應的數組位置是否為1 ,為1 說明就存在,為0 說明就不存在。這樣簡單的bloomfilter就實現了,bloomfiler 是有錯誤率,可以考慮增加數組長度和散列函數的數量來提供準確率,具體可以百度或者google,今天在這裡就不講了。
下面來看看利用bloomfiler 來防止快取穿透的流程,看圖說話:
bloomfiler的初始化可以透過一個定時任務來讀取db,初始化bit數組的大小,預設值都是為0,表示不存在,然後每個都計算散列值對應的數組位置,然後插入到bit 數組中。
請求流程,看圖:
#如果不利用bloomfiler 過濾器,對於一個資料庫裡根本不存在的key,其實白白浪費了兩次IO,一次查詢redis,一次查詢DB,有了bloomfiler ,那麼就節省了這兩次無用的IO,減少後端redis 和DB 資源的浪費。
今天我們聊了redis快取 的高頻的面試和實戰中遇到的問題以及解決方案。
快取雪崩
解決方案:
#在設定失效時間段的時候,加上一個時間的隨機數,可以幾分鐘之內的都可以。
以及如果真的雪崩了怎麼辦的問題,可以採用限流的方式。
快取擊穿
解決方案:
定時更新熱點key ,這裡著重在延遲佇列。
以上是聊聊Redis中的快取雪崩、快取擊穿和快取穿透的詳細內容。更多資訊請關注PHP中文網其他相關文章!