資料出自博主【敖丙】的《吊打面試官》系列(以下);html
如有侵權,勞請留言刪除,拜謝!面試
《吊打面試官》系列-緩存雪崩、擊穿、穿透、redis
前言:昨晚收到消息,公司(外包)招標(甲方爸爸)沒招到,外包的娃真心累,我勒個。。。話很少說,趕忙把之前的知識點概括總結一下,面試準備一下。數據庫
答:我瞭解的。目前電商首頁以及熱點數據都會去作緩存,通常緩存都是定時任務去刷新,或者是查不到以後去更新,定時任務刷新就有一個問題。緩存
舉個簡單例子:若是全部首頁的key失效時間都是12小時,中午12點刷新,我0點開始搶單活動,大量用戶涌入,假設當時每秒6000個請求,原本緩存能夠扛住每秒5000個請求,可是緩存當時全部的 Key 都失效了。此時,1秒 6000個請求所有打到數據庫,數據庫必然扛不住,它會報一下警,但實際狀況多是DBA都沒反應過來數據庫就直接掛了。此時,若是沒有什麼特別的方案來處理這個故障,DBA很着急,重啓數據庫,可是數據庫立刻又被新的流量打死了。這就是我理解的緩存雪崩。安全
同一時間緩存大面積失效,那一瞬間Redis跟沒有同樣,這個數量界別的請求直接打到數據庫幾乎是災難性的。試想一下,若是打掛的是一個用戶服務的庫,那其餘依賴它的庫全部的接口幾乎都會報錯,若是沒有作熔斷等策略,基本上就是一瞬間掛一片的節奏,任你怎麼重啓用戶都會把你打掛,等你重啓好的時候,用戶早睡覺去了,而且對咱們的產品失去了信息。服務器
答:處理緩存雪崩想對簡單,在批量往Redis存數據的時候,把每一個Key的失效時間都加個隨機值就好,這樣就能夠保證數據不會在同一時間大面積失效,我相信,Redis這點流量仍是頂得住的。數據結構
setRedis( Key, value, time+Math.random() * 1000 );
若是Redis是集羣部署,講熱點數據均勻分佈在不一樣的Redis庫中也能避免所有失效問題,不過在生產環境中操做集羣的時候,單個服務都是對應的單個Redis分片,是爲了方便數據的管理,可是也一樣有可能回失效這樣的弊端,失效時間隨機是個好策略。併發
或者設置熱點數據永遠不過時,用更新操做就更新緩存就行了(好比運維更新了首頁商品,那你刷下緩存就完事了,不要設置過時時間),電商首頁的數據也能夠用這個操做,保險。
答:有了解的。先說說緩存穿透吧。緩存穿透是指緩存和數據庫中都沒有數據,而用戶不斷髮起請求,咱們數據庫的 id 都是1開始自增上去的,若是發起 id = -1 的數據 或 id 爲特別大(不存在)的數據,這是用戶就極可能是攻擊者,攻擊會致使數據庫壓力過大,嚴重會擊垮數據庫。
小點的單機系統,可能用 postman 就能搞死,好比本身買的雲服務。
像這種沒有對參數進行校驗,數據庫 ID 都是大於0的,咱們一直用小於 0 的參數去請求,每次都能繞開Redis直接打到數據庫,數據庫也查不到,每次都這樣,併發高點就容易崩掉了。
至於緩存擊穿嘛,這個和緩存雪崩有點像,可是又有點區別,緩存雪崩是由於大面積的緩存失效,打崩了DB,而緩存擊穿不一樣的是緩存擊穿是指一個Key很是熱點,在不停地扛着大併發,大併發集中對一個點進行訪問,當這個Key在失效的瞬間,持續的大併發就穿透緩存,直接請求數據庫,就像一個無缺無損的桶上鑿開了一個洞。
答:緩存穿透是參數繞開緩存直接打在數據庫上,我會在接口層增長校驗,好比對用戶權限校驗,接口參數作校驗,不合法的參數直接Return,好比 id 作基礎校驗,id<=0的直接攔截等等。
這裏我想補充一點,就是咱們在開發的時候,要保持一顆「不信任」的心,就是不要相信任何調用方,好比我提供API接口出去,我須要這幾個入參,那做爲被調用方,任何可能的參數狀況都應該被考慮到、並作校驗,由於我不相信調用我接口的人,我不知道他會傳什麼參數給我。
舉個簡單例子,我這個接口是分頁查詢的,可是我沒有對分頁參數的大小作限制,調用的人萬一 一口氣查 Interger.MAX_VALUE 一次請求就須要我幾秒,多幾個併發我接口就掛了。是公司同事調用還好,大不了發現了改掉就是了,可是若是是黑客或者競爭對手,那問題就大了。在雙十一或者搶單活動的時候調用這個接口,那損失就大了。
從緩存取不到的數據,在數據庫中也沒有取到,這時也能夠將對應Key的Value對寫爲null、「位置錯誤」、「稍後重試」這樣的值(具體和產品溝通),或者看具體的場景,緩存的時間能夠設置短點,如30秒(設置太長會致使正常狀況下也無法適用)。
這樣能夠防止攻擊用戶反覆用同一個 id 暴力攻擊,可是咱們要知道正經常使用戶是不會在單秒內發起這麼屢次請求的,那網關層Nginx 咱們也能夠添加配置項,可讓運維人員對單個 IP 每秒訪問次數超出閥值的 IP 都拉黑。
答:記得Redis還有一個高級用法布隆過濾器(Bloom Filter),這個也能很好地防止緩存穿透的發生,他的原理也很簡單,就是利用高效的數據結構和算法快速判斷出你這個Key是否在數據庫中存在,不存在你Return就行了,存在你就去查了DB刷新Key、Value,再Return。
緩存擊穿的話,設置熱點數據永遠不過時,或者加上互斥鎖就能搞定了。(代碼以下)
1 /** 2 * 獲取數據 3 * @param key 查詢參數 4 * @return data 數據 5 * @throws InterruptedException 異常 6 * @author 檸檬先生 7 */ 8 public static String getData(String key) throws InterruptedException { 9 // 從redis查詢數據 10 String result = getDataFromKV(key); 11 // 校驗數據 12 if (StringUtils.isBlank(result)){ 13 // 獲取鎖 14 if (reenLock.tryLock()) { 15 // 去數據庫查詢 16 result = getDataFromDB(key); 17 // 校驗 18 if (StringUtils.isNotBlank(result)){ 19 // 保存到緩存中; 20 setDataToKV(key, result); 21 } 22 // *釋放鎖 正常會再finally裏面釋放; 23 reenLock.unLock(); 24 }else { 25 // 睡一下子再拿 26 Thread.sleep(100L); 27 result = getData(key); 28 } 29 } 30 return result; 31 } 32 //這裏的鎖是單機版玩法.,分佈式鎖仍是得靠lua腳本這樣的;
以上介紹了Redis的雪崩、擊穿、穿透,三者其實都差很少,可是又有些區別,在面試中這是問到緩存必問的,由於緩存的雪崩、擊穿、穿透,是緩存最大的問題,要麼不出現,一旦出現就是致命性的問題,因此面試官必定會問。
咱們學習的時候必定要理解是怎麼發生,以及怎麼去避免的,發生以後怎麼去搶救,你能夠不是瞭解很深刻,可是你必定不能什麼都不去想,面試有時候不必定是對只是面的拷問,或許是對你態度的拷問,若是你思路清晰,而後知其然還能知其因此然那就更棒了,還能知道怎麼預防,那offer就是手到擒來了。
通常避免以上狀況發生咱們要從3個時間段去分析:
上面提到的限流組件,能夠設置每秒的請求,有多少能經過組件,剩餘未經過的請求,怎麼辦?走降級!能夠返回一些默認值,或者友情提示,或者空白的值。
這樣作的好處是:數據庫絕對不會死,限流組件確保了每秒只有多少個請求能經過。只要數據庫不死,對用戶來講, 3/5 的請求都是能夠被處理的。只要有 3/5 的請求能夠被處理,就意味着系統沒有死,對用戶來講,可能就是點擊幾回刷不出來頁面,可是多點幾回就能夠刷出來一次。
這個在目前主流的互聯網大廠裏面是最多見的,之前遇到過,某明星爆出什麼事情,會發現微博怎麼刷都是空白界面,可是有的人有直接進去了,多刷幾回又出來了,這就是作了降級,犧牲部分用戶體驗來換取服務器的安全,還行。
常見的5種:字符串(String),散列(Hash), 列表(List),集合(Set),有序集合(SortedSet)等;除此以外,還有HyperLogLog、Geo、Pub/Sub。
注:以前項目組也有碰見過 BloomFilter(布隆過濾器),這個能很好地防止緩存穿透的發生,它的原理也很簡單,就是利用高效的數據結構和算法快速判斷出你這個 Key 是否在數據庫中存在,不存在你 Return 就行了,存在你就去查了DB刷新Key、Value,再Return。
TODO
答:傳統的關係型數據庫(如MySQL)已經不適用全部的場景了,好比美雲銷搶單活動的庫存扣減,APP首頁的訪問流量高峯等等,都容易把數據庫打崩,因此引入了緩存中間件,目前市場上比較經常使用的緩存中間件有Redis 和 Memcached ,不過綜合考慮了他們的優缺點,最後選擇了Redis 。
答:
Redis的好處:
Redis 相比 Memcached 的優點:
答:若是大量的 Key 過時時間設置得過於集中,到過時的那個時間點,Redis可能回出現短暫的卡頓現象,嚴重的話可能會出現雪崩。咱們通常在過時時間上加一個隨機值,使得過時時間分散一些,這樣也能有效地預防雪崩。
電商首頁常常會使用定時任務刷新緩存,可能大量的數據的失效時間都十分集中,若是失效時間同樣,又恰好在失效的時間點有大量用戶涌入,就有可能形成緩存雪崩。
答:先拿 setnx 來爭搶鎖,搶到以後,再用 xepire 給鎖加一個過時時間防止鎖忘記釋放。
答:(此時能夠適當作出驚訝的反饋)對哦,若是遇到這種狀況,這個鎖就永遠得不到釋放了。緊接着你須要抓一抓本身的腦殼,故做思考片刻,好像接下來的結果是你主動思考出來的,而後繼續回答:我記得 set 指令有很是複雜的參數,這個應該是能夠同時把 setnx 和 expire 合成一條指令來用的,這樣就不會出現這種狀況了。
傳送門:每秒上千訂單的場景下,如何對分佈式鎖進行高併發優化?
答:使用keys指令能夠掃出指定模式的key列表。
答:這個時候要回答 Redis 關鍵的一個特性:Redis 是單線程的。keys 指令會致使線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務纔會恢復。這個時候可使用 scan 指令,scan 指令能夠無阻塞地提取出指定模式的key列表,可是會有必定的重複機率,在客戶端作一次去重就能夠了,可是總體所花費的時間會比直接用 keys 指令長。
不過,增量式迭代命令也不是沒有缺點的:舉個例子,使用 SMEMBERS 命令能夠返回集合鍵當前包含的全部元素,可是對於 SCAN 這類增量式迭代命令來講,由於對鍵進行增量式迭代的過程當中,鍵可能會被修改,因此增量式迭代命令只能對被返回的元素提供有限的保證。
答:通常使用 List 結構做爲隊列,rpush 生產消息,lpop 消費消息。當 lpop 沒有消息的時候,要適當 sleep 一下子再試。
答:list 還有個指令叫 blpop,在沒有消息的時候,它會阻塞住,直到消息到來。
答:使用 pub/sub 主題訂閱模式,能夠實現 1:N 的消息隊列。
答:在消費者下線的狀況下,生產的消息會丟失,得使用專業的消息隊列,如 RocketMQ 等。
答:(穩住,淡定)使用 sortedset,那時間戳做爲 score,消息內容做爲 key 調用 zadd 來生產消息,消費者用 zrangebyscore 指令獲取N秒以前的數據輪詢進行處理。
答:RDB作鏡像全量持久化,AOF作增量持久化。由於RDB會耗費較長時間,不夠實時,在停機的時候會致使大量數據丟失,因此須要AOF來配合使用。在 Redis 示例重啓時,會使用RDB持久化文件從新構建內存,再使用AOF重放近期的操做指令來實現完整恢復重啓以前的狀態。
這個很好理解,把RDB理解爲一整個表全量的數據,AOF理解爲每次操做的日誌就行了,服務器重啓的時候先把表的數據全搞進去,可是他可能不完整,你再回放一下日誌,數據不就完整了嘛。不過Redis自己的機制是AOF持久化開啓且存在AOF文件時,優先加載AOF文件;AOF關閉或者AOF文件不存在時,加載RDB文件;加載AOF/RDB文件域後,Redis啓動成功;AOF/RDB文件存在錯誤時,Redis啓動失敗病打印錯誤信息。
答:取決於AOF日誌 sync 屬性的配置,若是不要求性能,在每條寫指令時都 sync 一下磁盤,就不會丟失數據。可是在高性能要求下每次都sync是不現實的,通常都是用定時sync,好比1s1次,這個時候最多就會丟失1s的數據。
答:給出兩個詞彙就能夠了,fork 和 cow 。fork 是指 redis 經過建立子進程來進行 RDB 操做,cow 是指 copy on write,子進程建立以後,父子進程共享數據段,父進程繼續提供讀寫服務,寫髒的也米恩數據會逐漸和子進程分離開來。
注:回答這個問題的時候,若是你還能說出 AOF 和 RDB 的優缺點,面試官可能會在這個問題上給你點贊,二者的區別仍是挺大的,並且涉及到Redis集羣的數據同步問題等等。
後期須要把這個補上。TODO
答:能夠將屢次 IO 往返的時間縮減爲一次,前提是 pipeline 執行的指令之間沒有因果關係性。使用 redis-benchmark 進行壓測的時候能夠發現影響 redis 的QPS峯值的一個重要因素是 pipeline 批次指令的數目。
答:Redis 可使用主從同步,叢叢同步。第一次同步時,主節點作一次 bgsave,並同時將後續修改操做記錄到內存 buffer,待完成後將RDB文件全量同步到複製節點,複製節點接受完成後將RDB鏡像加載到內存。加載完成後,再經過主節點將期間修改的操做記錄同步到複製節點進行重放就完成了同步過程。後續的增量數據經過AOF日誌同步便可,有點相似數據庫的 binlog。
答:Redis Sentinal 着眼於高可用,在 master 宕機時會自動將 slave 提高爲 master,繼續提供服務。
Redis Cluster 着眼於擴展性,在單個 redis 內存不足時,使用 Cluster 進行分片存儲。
在面技術的時候,無論是Redis仍是什麼問題,若是你能舉出實際的例子,或者是直接說本身開發過程的問題和收穫,會給面試官留下很好的印象,回答邏輯性要強一些,不要東一點西一點,容易把本身繞暈。
還有一點就是,我問你爲啥要用Redis,你不要一上來就直接回答問題了,你能夠這樣回答:
首先,咱們的項目DB遇到了瓶頸,特別是秒殺和熱點數據(、搶單活動)這樣的場景,DB基本就扛不住了,那就須要緩存中間件的加入,目前市面上的緩存中間件有 Redis 和 Memcached,它們的優缺點…,綜合這些,再結合咱們項目的特色,最後咱們在技術選型的時候選了Redis。
若是這樣有條不紊,有理有據地回答了個人問題,並且還說出這麼多我問題外的知識點,我會以爲你不僅是一個會寫代碼的人,你邏輯清晰,你對技術選型,對中間鍵、對項目都有本身的理解和思考,是我的才。
鳴謝博主:敖丙