老司機帶你玩轉面試(2):Redis 過期策略以及快取雪崩、擊穿、穿透

極客挖掘機發表於2020-07-14

前文回顧

建議前一篇文章沒看過的同學先看下前面的文章:

「老司機帶你玩轉面試(1):快取中介軟體 Redis 基礎知識以及資料持久化」

過期策略

Redis 的過期策略都有哪些?

在聊這個問題之前,一定要明確的一件事情是,如非必要,任何進入快取的資料都應該設定過期時間,因為記憶體的大小是有限的,一臺機器可能就那麼幾十個 G ,你不能拿記憶體和硬碟比,一臺機器硬碟幾個 T 都是灑灑水,只要想裝,幾十個 T 都裝得下,關鍵還不貴。

Redis 設定刪除策略,主要有兩種思路,一種是定期刪除,另一種是惰性刪除。

定期刪除:

設定一個時間,在 Redis 中預設是每隔 100ms 就隨機抽取一些設定了過期時間的 key,檢查其是否過期,如果過期就刪除。

注意這裡是隨機抽取一些設定了過期時間的 key ,而是掃描所有,試想這樣一個場景,如果我有 100w 個設定了過期時間的 key ,如果每次都全部掃描一遍,基本上 Redis 就死了, CPU 的負載會非常的高,全部都消耗在了檢查過期 key 上面。

惰性刪除:

惰性刪除的意思就是當 key 過期後,不做刪除動作,等到下次使用的時候,發現 key 已經過期,這時不在返回這個 key 對應的 value ,直接將這個 key 刪除掉。

這種方式有一個致命的弱點,就是會有很多過期的 key-value 明明已經到了過期時間,缺還在記憶體中佔著使用空間,大大降低了記憶體使用效率。

所以 Redis 的過期策略是:定期刪除 + 惰性刪除。

實際上簡單的定期刪除 + 惰性刪除還是會存在問題,定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉,然後我們也沒有及時的去做檢查,也沒有做惰性刪除,此時的結果就是大量過期 key 堆積在記憶體裡,導致 Redis 的記憶體被耗盡。

咋整?答案是走記憶體淘汰機制。

記憶體淘汰機制都有哪些?

Redis 記憶體淘汰機制有以下幾個:

  • noeviction: 當記憶體不足以容納新寫入資料時,新寫入操作會報錯。這個一般沒啥人用吧,太傻了。
  • allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)。
  • allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個 key。這個一般沒人用吧,為啥要隨機,肯定是把最近最少使用的 key 給幹掉啊。
  • volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的 key(這個一般不太合適)。
  • volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個 key。這個一般也沒人用吧。
  • volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的 key 優先移除。

接下來,面試官就有可能要求手寫一個 LRU 演算法了,從頭開始寫確實有點困難,那程式碼量太大了,現場寫也不大現實,但是藉助 JDK 的現有的資料結構,寫一個 LRU ,還是應該掌握下的,篇幅原因,我就不貼程式碼了,這一段程式碼一搜一大把,大家自己百度吧。

快取雪崩、擊穿、穿透

這幾個問題也是在面試中經常會問到的,因為在實際的使用過程中,這一部分內容往往牽動了整個系統的安全性以及穩定性。

快取雪崩

先了解下什麼是快取雪崩?

我先描述兩個使用場景:

場景一:

線上正在執行的系統,突發狀況, Redis 掛掉了,導致大量的資料訪問不走 Redis ,直接落在了 DB 上,DB 扛不住這麼大量的訪問,直接崩掉了,這時如果 BD 是獨立使用還好,如果不是獨立的, DB 還和其他業務功能共享,那麼勢必會導致其他使用同一 DB 的業務功能一起崩掉,又會導致依賴於這些業務功能的其他系統接著完蛋,結果就是所有的系統和功能一起上天,這時,可能祭天一個程式設計師就不夠了,整個 IT 部都要一起上天了。

場景二:

比如說電商平臺,首頁的熱點資料都是放在快取中的,早晨 8 點重新整理了一批熱點資料,設定有效期是 4 個小時,而在這 4 個小時中,並沒有重新整理新的熱點資料,恰巧在中午 12 點的時候有一個秒殺活動。

悲慘的結果已經可以預見了,當時間走到 12 點,熱點資料集體失效,而秒殺活動導致大量的使用者瘋狂的湧入,熱點資料不存在,請求的資料直接落到了 DB 上,導致 DB 崩掉,重複一遍和場景一一樣的慘況,整個 IT 部被拿來送上太陽,和我們的川普老師肩並肩。

上面兩個場景的結果都是一樣的,因為 Redis 的功能不可用,導致 BD 上天,從而導致 IT 部集體飛昇。最坑的是這種事情發生後,還沒有一個簡單易行的處理方案,因為單純的重啟恢復服務這個套路已經不適用了,流量大的情況下,服務是起不來的好哇,服務剛起來就被流量幹爬下,這種事情在國內某知名網際網路公司發生過。只能是先在閘道器把所有流量攔截掉,然後恢復後端服務,並同步補充 Redis 的熱 key ,等這些事情都 ok 以後,才能將流量放進來重新恢復服務,這麼一套搞下來,幾個小時沒有了吧,當然,這個耗時視公司的不同而不同。

那麼這種情況應該如何處理,先說說場景二,首先是不能使得所有的熱點 key 同時失效,這裡簡單加一個隨機數就好了,尤其是電商首頁這種場景,直接設定熱 key 永不失效都是可以的,要更新資料就直接更新,唯一的好處就是保險。

那麼場景一這種情況我們還有什麼解決方案呢?

首先,第一步要做到的就是 Redis 的高可用,隨便什麼方案,但絕對不能是單機,叢集怎麼也比單機掛掉的概率要小得多。

接下來,就是程式要啟用本地 ehcache 快取,還要加上服務限流與降級,服務限流降級前 Netflix 提供的 hystrix 後有 Alibaba 提供的 Sentinel ,選用什麼看自己的使用場景,有了這個,至少保證了 BD 不會被一下打死,至少保證了服務的部分可用,哪怕只能保證 20% 的可用,對於使用者來講,就是多重新整理幾次頁面的事情。

最後就是一定要開啟 Redis 的持久化, Redis 一旦重啟,自動從磁碟上載入資料,快速恢復快取資料。

快取穿透

什麼是快取穿透?

還是講一個場景,比如我的資料 id 是從 0 開始的自增序列,普通人請求沒啥問題,每次都是用一個正常的 id 來進行訪問,但是總有刁民搞事情,每次訪問都使用 id 為 -1 來進行訪問,因為 -1 並不存在,所有在 Redis 中也不會進行快取,每次查詢 DB 也查不到結果,這種請求直接無視了快取的存在,直接作用於 DB ,只要訪問量超過一定的數量,就會直接把資料庫打死。

這種場景的解決方案其實很簡單,比如我們可以在程式中對資料的合法性進行校驗,如果資料不合法直接返回,比如上面的例子,如果 id 小於 0 ,直接返回失敗。

但是單純的這麼操作並不能解決所有的問題,有時候我們並不好判斷資料的合法性,就比如上面那個場景,比如資料中 id 最大隻有 500 ,但是某些刁民老用一些大值進行請求,比如說 1000 ,這時雖然資料合法,但還是會落在 DB 上,這裡我再提供一個簡單粗暴的方式,當發現資料沒查到資料的時候,在快取中對這個 key 設定空的 value ,下次再進來就會去走快取而不會落到 BD 上,這樣也能有效防止快取穿透。

快取擊穿

快取擊穿和雪崩是有點像的,雪崩是大批量的熱 key 集體失效或者是不可用,導致請求直接落在 DB 上而打崩 DB ,最終導致整個系統的癱瘓。

而快取擊穿是指在某些情況下,會有一個極熱極熱的 key ,在扛著非常非常大的併發,突然過期失效,導致所有的請求在一瞬間落在了 DB 上,瞬間擊垮 DB 導致全域性崩盤。

看到這不知道大家想到了誰,反正我想到的是那個號稱七星軌的軟體,當然哈,人家的問題並不是快取擊穿,是真的扛不住,快取直接被打崩了,畢竟我國的全民吃瓜,這個流量還是相當猛的。

都說到這了順便聊聊它們的情況,第一次是不是快取擊穿現在已經無從考證了,基於這麼多次的崩潰看下來,它們家的問題並不是軟體層面的問題了,是硬體直接就不夠,貌似每次解決問題都是直接聯絡阿里雲擴容,阿里雲容量一擴上去問題立馬就沒了。

你如果非要問硬體為什麼不夠,很簡單麼,那麼多硬體不要錢啊,頻寬不要錢的啊,你給啊,現在硬體頻寬開那麼高,又不是天天都有明星出軌我們都有瓜吃,空出來的浪費啊。

現在這種操作就挺好,平時硬體夠用就行,明星出軌臨時擴容,事件熱點降低後再縮容回去,最近幾次我看到擴容的速度明顯快很多了,好幾次從我知道崩了不能用到服務恢復只有不到半小時(也有可能是我訊息閉塞),感覺阿里雲的團隊和某博的團隊在經歷了這麼多大瓜以後已經能配合的非常默契了。

扯回來,我們接著說快取擊穿怎麼處理。

不同場景下的解決方式如下:

  • 若快取的資料是基本不會發生更新的,則可嘗試將該熱點資料設定為永不過期。
  • 若快取的資料更新不頻繁,且快取重新整理的整個流程耗時較少的情況下,則可以採用基於 Redis、zookeeper 等分散式中介軟體的分散式互斥鎖,或者本地互斥鎖以保證僅少量的請求能請求資料庫並重新構建快取,其餘執行緒則在鎖釋放後能訪問到新快取。
  • 若快取的資料更新頻繁或者在快取重新整理的流程耗時較長的情況下,可以利用定時執行緒在快取過期前主動地重新構建快取或者延後快取的過期時間,以保證所有的請求能一直訪問到對應的快取。

相關文章