1. 程式人生 > 其它 >Redis掛了,流量把資料庫也打掛了,怎麼辦?

Redis掛了,流量把資料庫也打掛了,怎麼辦?

看似面試場景題,實則必背八股文。

你好呀,我是歪歪。

是這樣的,前幾天有個讀者給我發訊息,說面試的時候遇到一個場景題:

他說他當時,一時間竟然找不到回答問題的角度,感覺自己沒有回答到點子上。

我仔細想了一下,確實是感到這個問題有一絲絲的奇怪,有一種讓人千言萬語,又突然懵逼不知從何說起的神奇力量。

為什麼這麼說呢?

我們先讀題啊,仔細的讀一遍題,我給你翻譯一下。

如果線上 Redis 掛了。然後所有請求打到資料庫導致資料庫也掛了。

這是啥?

Redis 掛了,不就是快取都沒了嗎?

快取都沒了,不就是快取雪崩了嗎?

快取雪崩了,不就導致資料庫掛了嗎?

一提到“快取雪崩”這四個字,快取穿透、快取擊穿這幾兄弟,是不是就立馬條件反射的出現在你的腦海裡面了,還順帶著對應的幾套解決方案。

然後就像背書似的,什麼快取全沒了,什麼快取沒有資料庫中有,什麼快取和資料庫中都沒有...

張口就是幾分鐘不帶停頓的。

另外關於快取擊穿和快取穿透,很多同學都會搞混。

你換一個記法,快取戳穿、快取戳透。

穿,只是穿過了快取。

透,是直接幹到底。

你細品,應該就不會記混。

除了上面的“Redis 快取三連擊”這一套八股文之外,還隱藏著另外一個八股文:

Redis 掛了,為什麼掛了?怎麼就掛了?是不是有單點問題?

這不就是問你 Redis 服務的高可用嗎?

說到 Redis 的高可用,腦子裡面必須馬上蹦出來主從、哨兵和叢集吧?

想到這些了,張口又是幾分鐘不帶停頓的。

但是這幾分鐘的千言萬語,馬上就被下面這個問題給幹懵逼了?

這時該怎麼進行恢復?

現在問你怎麼恢復,就是事中的事兒了。

你得先說怎麼恢復,再說怎麼預防。

你要是上來就回答前面說的什麼“快取三連擊”、“高可用架構”,還包括大多數同學能想到的多級快取、限流措施、服務降級、熔斷機制,這些都有點牽強。

因為畢竟這些手段都是事前的預防措施,上來就說這些背書痕跡比較明顯。

答肯定是要答的,從事中恢復過度到事前預防方案,而且重點就是事前預防,那麼我們怎麼過度的自然一點呢?

先說事中怎麼恢復,其實我覺得幾句話就說完了。

服務掛了啊,老哥,還能怎麼恢復,當然是重啟服務啊。

站在運維人員的角度,當然優先考慮是先把 Redis 和資料庫服務重新啟動起來啦。

但是啟動之前得先做個小操作,把流量摘掉,可以先把流量攔截在入口的地方,比如簡單粗暴的通過 Nginx 的配置把請求都轉到一個精心設計的錯誤頁面,就是說這麼一個意思。

這樣做的目的是為了防止流量過大,直接把新啟動的服務,啟動一個打掛一個的情況出現。

要是啟動起來又扛不住了,請在心裡默唸分散式系統三大利器:

不行就加錢,堆機器嘛。

要覺得堆機器沒啥技術含量,你就再從快取預熱的角度答一個。

就是當 Redis 服務重新啟動後,通過程式先放點已知的熱點 key 進去後,系統再對外提供服務,防止快取擊穿的場景。

而且上面這一系列操作其實和開發人員的關係不大,主要是運維同學乾的事兒。

開發同學最多就是在設計服務的時候做到服務無狀態,以達到快速水平擴容的目的。

至於怎麼去快速水平擴容,那是運維同學的事兒,暫時不要去搶別人的飯碗。

答到這,你就可以用“但是”來過度到事前預防,開始自己的表演了。

故作沉思的對面試官說“but”了:

我覺得從技術方案的角度來說,我們應該做到事前預防。

這一切的問題都是因為 Redis 崩了,也就是發生了快取雪崩。

在高併發的情況下,除了快取雪崩,我們還必須得考慮到快取的擊穿、穿透問題。

而且 Redis 為什麼會崩了?是不是使用姿勢不對?是不是沒有保證高可用?

服務中是不是需要考慮限流或者熔斷機制,最大程度的保護程式的執行?

或者我們是否應該建立多級快取的機制,防止 Redis 掛掉之後,大批流量直接打到 MySQL 服務導致資料庫的崩盤?

至此,“but”完成,答題的方向從事中恢復,轉向了事前預防,進入了我們的強項,八股文專場,然後就可以開始“背誦”了。

我這裡簡單的聊一下快取問題三連擊和 Redis 的高可用。

至於多級快取,可以看看我之前發的這篇文章:《這波舒服了,落地多級快取!》

快取擊穿

先說一下快取擊穿的概念。

快取擊穿是指一個請求要訪問的資料,快取中沒有,但資料庫中有的情況。

這種情況一般來說就是快取過期了。

但是這時由於併發訪問這個快取的使用者特別多,這是一個熱點 key,這麼多使用者的請求同時過來,在快取裡面沒有取到資料,所以又同時去訪問資料庫取資料,引起資料庫流量激增,壓力瞬間增大,直接崩潰給你看。

所以一個數據有快取,每次請求都從快取中快速的返回了資料,但是某個時間點快取失效了,某個請求在快取中沒有請求到資料,這時候我們就說這個請求就"擊穿"了快取。

針對這個場景,對應的解決方案一般來說有三種。

  • 第一個就是隻放行一個請求到資料庫,然後做構建快取的操作。

多個請求只放行一個,怎麼做?

就藉助 Redis setNX 命令設定一個標誌位就行。設定成功的放行,設定失敗的就輪詢等待。

  • 第二個解決方案就是後臺續命。

這個方案的思想就是,後臺開一個定時任務,專門主動更新即將過期的資料。

比如程式中設定 why 這個熱點 key 的時候,同時設定了過期時間為 10 分鐘,那後臺程式在第 8 分鐘的時候,會去資料庫查詢資料並重新放到快取中,同時再次設定快取為 10 分鐘。

怎麼樣,是不是有點 Redisson 分散式鎖看門狗的味道?

我覺得思想是一脈相承的。

只是方案落地的時候,從程式碼編寫的角度來說稍微麻煩了一點。

我曾經也藉助這個思想開發過一個流水號系統。

大概是這樣的。

流水號系統,屬於非常關鍵的系統,為了降低資料庫異常對服務帶來的衝擊,所以服務啟動後會就會為每種業務系統都預先在快取中快取 5000 個流水號。

然後後臺 Job 定時檢查快取中還剩下多少流水號,如果小於 1000 個,則再預先生成新的流水號,補充到快取中,讓快取中的流水號再次回到 5000 個。

這樣做的好處就是資料庫異常後,我至少保證還有 5000 個快取可以保證上游業務,我有一定的時間去恢復資料庫。

這也算是一種後臺續命的思想。

  • 第三個方法就簡單了:永不過期。

快取為什麼會被擊穿,是不是因為設定了超時時間,然後被回收了?

那我不設定超時時間不就行了?

如果結合實際場景你用腳趾頭都能想到這個 key 一定會是個熱點 key,會有大量的請求來訪問這個資料。而且這個 key 對應的 value 不會發生變化。

對於這樣的資料你還設定過期時間幹什麼?

直接放進去,永不過期。

其實上面的後臺續命思想的最終體現是也是永不過期。

只是後臺續命的思想,會主動更新快取,適用於快取會變的場景。會出現快取不一致的情況,取決於你的業務場景能接受多長時間的快取不一致。

總之,具體情況,具體分析。

但是思路要清晰,最終方案都是常規方案的組合或者變種。

快取穿透

那麼啥又是快取穿透呢?

快取穿透是指一個請求要訪問的資料,快取和資料庫中都沒有,而使用者短時間、高密度的發起這樣的請求,每次都打到資料庫服務上,給資料庫造成了壓力。

一般來說這樣的請求屬於惡意請求。

就比如說,我這是一個技術公眾號,你明明知道我沒有,但是你非要來我這裡買一瓶啤酒,惡意請求。

怎麼解決呢?

兩個方案。

第一個快取空物件。

就是在資料庫即使沒有查詢到資料,我們也把這次請求當做 key 快取起來,value 可以是 NULL。

下次同樣請求就會命中這個 NULL,快取層就處理了這個請求,不會對資料庫產生壓力。

這樣實現起來簡單,開發成本很低。

但這樣隨之而來的一個面試題必須要注意一下:

對於惡意攻擊,請求的時候key往往各不相同,且只請求一次,那你要把這些 key 都快取起來的話,因為每個 key 都只請求一次,那還是每次都會請求資料庫,沒有保護到資料庫呀?

這個問題,布隆過濾器,瞭解一下?

關於布隆過濾器我之前寫過這篇文章,可以看看:《布隆,牛逼!布穀鳥,牛逼!》

布隆過濾器的特性是說某個值存在時,這個值可能不存在。當它說不存在時,那就肯定不存在。

所以可以基於這個特性,把已有資料都構建到布隆過濾器裡面去。

然後它可以幫忙擋住絕大部分的攻擊。

但是還有個比較容易忽視的連環炮問題:

面試官:布隆過濾器容量有限且不支援刪除,隨著裡面內容的增加,誤判率就會隨之上升。請問,這個問題你們是怎麼解決的?

也是兩個答題方向。

首先,不支援刪除的話,就換一個支援刪除的布隆過濾器的輪子咯。

比如我前面的文章中提到的布穀鳥過濾器。

或者就是提前重構布隆過濾器。

比如在容量達到 50% 的時候,就申請一個新的更大的布隆過濾器來替換掉之前的過濾器。

只是需要注意的是,重建你得知道有那些資料需要進行重建的,所以你得有個地方來記錄。

比如就是 Redis、資料庫,甚至記憶體快取都可以。

沒落地過沒關係,你底氣十足的回答就行了。

你要相信,面試官八成也沒落地過,你們看的說不定都是同一份資料呢。

快取雪崩

快取雪崩是指快取中大多數的資料在同一時間到達過期時間,而查詢資料量巨大,這時候,又是快取中沒有,資料庫中有的情況了。

請求都打到資料庫上,引起資料庫流量激增,壓力瞬間增大,直接崩潰給你看。

和前面講的快取擊穿不同的是,快取擊穿指大量的請求併發查詢同一條資料。

快取雪崩是不同資料都到了過期時間,導致這些資料在快取中都查詢不到,

雪崩,還是用的很形象的。

防止雪崩的方案簡單來說就是錯峰過期。

在設定 key 過期時間的時候,在加上一個短的隨機過期時間,這樣就能避免大量快取在同一時間過期,引起的快取雪崩。

如果發了雪崩,我們可以有服務降級、熔斷、限流手段來拒絕一些請求,保證服務的正常。

但是,這些對使用者體驗是有一定影響的。假設我們的程式有這的邏輯,也是拿來兜底的,從使用者的角度來說,是不希望走到這樣的邏輯中去。

所以,還是以預防雪崩為主。

最後還有一種雪崩,就是整個 Redis 服務都掛了。

所以,接下來就要聊聊 Redis 服務的高可用架構了。

Redis 高可用架構

聊到 Redis 高可用架構,大家基本上都能想到主從、哨兵、叢集這三種模式。

主從結構很簡單,就不說了,其弊端主要是出現故障的時候需要人工介入干預,需要人工介入的,就不是真正的高可用。

哨兵和叢集這兩種是寫在官網上的方案:

https://redis.io/topics/introduction

上面劃線的話翻譯過來就是:Redis 通過 Redis 哨兵(Sentinel)和 Cluster 叢集提供高可用性(high availability)。

其中,哨兵是官方推薦的高可用方案(official high availability solution):

所以主要說一下哨兵模式。

哨兵是用來管理多個 Redis 伺服器的,我從《Redis開發與運維》一書中,拍個照片給你看看:

它主要執行三種類型的任務:

  • 監控(Monitoring): Sentinel 會不斷地檢查你的主伺服器和從伺服器是否運作正常。
  • 提醒(Notification): 當被監控的某個 Redis 伺服器出現問題時, Sentinel 可以通過 API 向管理員或者其他應用程式傳送通知。
  • 自動故障遷移(Automatic failover): 當一個主伺服器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會將失效主伺服器的其中一個從伺服器升級為新的主伺服器, 並讓失效主伺服器的其他從伺服器改為複製新的主伺服器; 當客戶端試圖連線失效的主伺服器時, 叢集也會向客戶端返回新主伺服器的地址, 使得叢集可以使用新主伺服器代替失效伺服器。

哨兵其實也是一個分散式系統,我們可以執行多個哨兵。

然後這些哨兵之間需要相互通氣,交流資訊,通過投票來決定是否執行自動故障遷移,以及選擇哪個從伺服器作為新的主伺服器。

哨兵之間採用的協議是 gossip,是一種去中心化的協議,達成的是最終一致性,非常有意思,

之前寫過的鳳凰架構裡面有關於 gossip 協議的介紹,可以看看:

https://icyfenix.cn/distribution/consensus/gossip.html

另外,如果主節點掛了,哨兵到底通過什麼規則選擇新的主節點,也就是選舉過程大致是怎麼樣的,也偶現於面試環節。

我以前就被問到過,幸好當時背的熟練。

簡單說一下規則,無它,背誦就完事了:

  • 在掛了的主節點下掛的從節點中,被標記為主觀下線、已斷線、或者最後一次回覆 PING 命令的時間大於五秒鐘的從節點都沒有資格參與選舉。
  • 在掛了的主節點下掛的從節點中,那些與掛了的主節點連線斷開的時長超過 down-after 配置指定的時長十倍的從節點都沒有資格參與選舉。
  • 經過上面這兩輪淘汰之後,剩下來的從伺服器中,選出複製偏移量(replication offset)最大的那個從伺服器作為新的主伺服器。如果複製偏移量不可用,或者從伺服器的複製偏移量相同,那麼帶有最小執行 ID 的那個從伺服器成為新的主伺服器。

其實執行上面這些操作的,是一個哨兵。而我們的哨兵一般是三個以上,那麼那個哨兵來執行這些操作呢?

其實這個哨兵也是需要從多個哨兵中被選舉一個出來的,被選出來的這個哨兵就是領頭哨兵(leader Sentinel)。

選舉領頭哨兵的時候,採取的是 Raft 演算法。

至於哨兵模式的搭建,一般來說是運維乾的事兒。

但是網上的搭建教程很多,能自己跟著教程親自搭一波那就更好了。

相信我,搭建的過程中你一定會碰到各種各樣的問題,而這些問題就是你的收穫。

回到開始

這一小節,我們回到最開始的這個面試題:

其實看到這個問題的時候,我就想到了老是被爆來爆去的微博。

剛好,這周又吃了一波吳某凡的瓜,當時還正在看女排的直播,看到報道的時候,表情大概是這樣的:

週六的晚上基本上就是帶著這個表情瓜田裡面上躥下跳的,真是太好吃了。

但是凡凡這一波,不知道是凡凡的流量不行了,還是微博的架構經受住了考驗。

微博竟然還比較順滑,沒有出現大範圍的、非常明顯的服務掛掉的現象。

我印象中最近一次微博掛的死死的,就是鹿晗關曉彤那事了。

倒不是因為我關注他們,而是我關注到了那天正在結婚的程式設計師。

要說這位丁振凱同學也真是太慘了。

結婚的時候碰上鹿晗公佈戀情。海外度假時撞上雙宋官宣。老婆待產的時候撞上華晨宇承認和張碧晨未婚生有一女。

這次我去看了,表現比較淡定。應該是在一手抱娃,一手擴容,順便吃瓜。

當年鹿晗這事,微博助手說掛掉是因為單條微博轉發、評論次數太多了。

這是不全面的,單純的轉發評論多,並不能壓垮大微博。而且鹿晗的那天微博應該也不是他所以的微博中轉發評論最多的一條。

是因為轉發、評論併發太高太高太高了,是我一輩子都接觸不到的瞬間流量。

吃瓜群眾也蜂擁而至,短時間內同時線上迅速爆漲,把伺服器幹掉了:

關於這個問題,我在知乎上看到一個評論,我覺得挺好的,搬運截圖一下:

https://www.zhihu.com/question/66346687

你看,這個場景和麵試官問的問題是不是有點相似?

強如微博,也是加了 1000 臺伺服器來應對這次流量洪峰。

所以,服務掛了怎麼辦?

重啟。

重啟還不行怎麼辦?

加錢,擴機器。

要是鹿晗關曉彤事件,著名狗仔卓偉能提前爆個料,打個提前量。

也許,微博就能抵抗的住那一波流量洪峰。

要是吳籤這事,北京警方能和微博提前通通氣,在釋出之前先通知一下微博的相關人員,哪怕提前10分鐘呢?

也許,就有更多的人能順暢絲滑的吃瓜。

最後說一句(求關注)

好了,看到了這裡安排個關注吧,周更很累的,需要一點正反饋。

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。