超高併發下,Redis熱點資料風險破解

Hello-Brand發表於2024-03-21

Redis24篇集合

1 介紹

作者是網際網路一線研發負責人,所在業務也是業核心心流量來源,經常參與 業務預定、積分競拍、商品秒殺等工作。
近期參與多場新員工的面試工作,經常就 『超高併發場景下熱點資料』 可用性保障與候選人進行討論。
本文聚焦一些關鍵點技術進行討論,並總結一些熱點場景的處理經驗。

2 業務基礎架構簡圖(假設)

image

3 超高併發下熱點資料的穩定性保障

3.1 命題背景

1000w+請求同時投向後端,如果快取未建立、失效,甚至快取服務故障,就會透過快取層直接投向資料庫。
可能會造成整體擊穿/雪崩,怎麼破?

3.2 各種業務場景及應對方案

3.2.1 規律性熱點資料預熱

無論是聚集式熱key,還是雜湊式熱key,只要是有一定規律性的,均可以做 預熱
既然是熱Key,那就想辦法儘可能讓它不進入MySQL,就不會對資料庫造成傷害,。
這種場景最常見的就是對一些字典資料做預熱,因為他們不容易改變,修改頻次較低,但又很容易在高峰期被群蜂請求(突發式的批次請求)。
電商領域比如: 商品種類、品牌型別、折扣規則。
辦公/教學領域比如:學校、年段、班級、學科、考試科目等。

一般來說如果10點是峰值期,那麼可以預先在8~10點期間,可以逐漸的把大部分快取建立起來。如圖:
image

3.2.2 非規律性熱點資料預熱

Redis + 應用層 加探測器,預判熱Key,並將探測到的熱Key進行預熱。
1、baidu實時熱搜
image
2. taobao商品排行
image
這種額外的開銷就是有一個實時計算的獨立元件,因為熱點新聞、熱點資料都有急劇突變的特性。比如weibo多次因為突發熱點新聞導致網站崩潰。

3.2.3 破解過期時間一致性問題

快取的建立過程都是雜湊的,但是如果長時間靜待都會被逐漸釋放。
比如釘釘、飛書的辦公場景,遇到夜晚低峰期、週末節假日,快取Key被逐步釋放之後。很容易在第二個工作日的早高峰造成大量建立快取,流量井噴。
解決方案除了前面我們提到的快取預熱之外,錯峰過期時間也是常規操作。
可以給快取設定過期時間時加上一個隨機值時間,使得每個key的過期時間分佈開來,不會集中在同一時刻失效。
隨機值我們團隊的做法是:n * 3/4 + n * random() 。所以,比如你原本計劃對一個快取建立的過期時間為8小時,那就是6小時 + 0~2小時的隨機值。
這樣保證了均勻分佈在 6~8小時之間。如圖:
image

3.2.4 過濾垃圾請求

一般情況下,我們取數先從快取中Get Key,不存在的時候再從資料庫中去獲取,但這很容易給攻擊者提供漏洞。
他可以瘋狂模擬一些不存在的Key,讓你進入資料庫去取數,這樣就可以拖垮你的資料庫,實現擊潰你係統的目的。
有效的辦法是在服務層先判斷這個Key的是否符合標準(比如滴滴的訂單資料快取包含時間戳+使用者ID的序列化),這樣可以過濾一部分無效攻擊。
但是如果他能夠破解你key的規則,依舊可以鑽漏洞。你可以在快取層上加一層過濾器,幫你Filter掉那些不合理的攻擊。
詳細可以參考我這篇《Redis系列16:聊聊布隆過濾器(原理篇)
image

3.2.5 訊息佇列和削峰

如果一個快取不存在(不存在、過期、被誤刪都有可能),但是同時有千萬請求投奔過來。
這時候關心是不是及時拿回正確資料已經不重要了,保住你的快取和資料庫不被擊穿才是關鍵。
佇列的目的是讓並行變成序列,這一定程度上降低系統處理使用者請求的吞吐能力,但是卻能很好的緩解你服務的壓力和風險。
image
如上圖:第一個請求B從資料庫中取,後面的C、A就是從快取服務中取了,壓力變小很多。

3.2.6 適當加鎖

分散式鎖場景,在訪問key之前,採用SETNX(set if not exists)來設定另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key。
這種現象是多個執行緒同時去查詢資料庫的這條資料,那麼我們可以在第一個查詢資料的請求上使用一個 互斥鎖來鎖住它。
其他的執行緒走到這一步拿不到鎖就等著,等第一個執行緒查詢到了資料,然後做快取。後面的執行緒進來發現已經有快取了,就直接走快取。
鎖不好的地方就是在其他執行緒在拿不到鎖的時候就等待,這個會造成系統整體吞吐量降低,使用者體驗度也不好。
這算是一種簡單明瞭的降級策略了。

3.2.7 限流策略

一樣是一種在流量井噴時保住服務不雪崩的有效方法,限流一般是從服務層去實現的。
Java服務的話可以使用 Hystrix進行限流 + 降級 ,比如一下子來了1W個請求,超過當前系統的吞吐承受能力,假設單秒TPS的能力只能是 5000個,那麼剩餘的 5000 請求就可以走限流邏輯。
可以設定一些預設值,然後呼叫我們自己降級邏輯去FallBack,保護最後的 MySQL 不會被大量的請求掛起。 除了Hystrix之外,阿里的Sentinel 和 Google的RateLimiter 都是不錯的選擇。
Sentinel 漏桶演算法
image
RateLimiter 令牌桶演算法
image

3.2.8 降級策略(備選快取)

你的快取層存在主備場景,他們之間定時非同步同步,所以允許存在短暫資料不一致的情況。
當你的主服務掛了之後,降級去讀備服務,資料時效性沒那麼高,但是也避免了資料庫被打穿的情況發生。
image

3.2.9 降級策略(客戶端快取)

參考Redis 6.0的 Client Side Cache,看我這篇《追求效能極致:客戶端快取帶來的革命》。
類似4.5做法,客戶端快取時效性會差一點,畢竟存在訂閱跟同步的過程,資料沒那麼新。但是避免大量的請求直接上快取服務,又因無效的快取服務又把壓力轉移給資料庫。
image

3.2.10 降級策略之空初始值

這是一種短效的降級方式:
如果一個快取失效的時候,有無數個請求狂奔而來,而第一個請求從進入快取池,判空,再到資料庫檢索,再查詢出結果並返回設定快取的這個過程裡,快取是不存在的。
這個就很危險,超高併發下這個短暫的過程足已讓千千萬萬請求投向資料庫。更別提這可能是個慢查詢,整個過程可能長達2s以上,那對資料庫是一種非常大的傷害。
業內有一種做法叫做空初始值,短暫的區域性降級來保證整個資料庫系統不被擊穿。大概流程如下:
image
可以看出,整個過程中我們犧牲了A、B、C、D的請求,他們拿回了一個空值或者預設值,但是這區域性的降級卻保證整個資料庫系統不被擁堵的請求擊穿。

3.2.11 高可用叢集和自動擴縮容

叢集模式和自動擴縮容模式從服務到快取到資料層都應該具備,否則無法根據流量來進行彈性伸縮,保持高可用。
如下圖, 藍色部件是擴容的部分,每一分層都有自己的動態擴容機制。
image
詳細可以參考筆者這幾篇文章。
雲原生:使用HPA和VPA實現叢集擴縮容
資料庫系列:資料庫高可用及無損擴容

3.2.12 雪崩之後的恢復

如果最終導致了快取雪崩,那麼重啟後快速的資料恢復也是我們核心的目標。
剛剛恢復重啟的快取服務,這時候資料都是空的,大量的請求流量帶來的快取重建(進而拉動資料庫流量)勢必會帶來壓力甚至二次雪崩。
這時候最好的辦法就是能夠有工具進行快取恢復,而不是從資料庫中去獲取資料來重建,這樣的過程漫長而負重。
這塊可以參考筆者的這兩篇文章:
Redis系列:RDB記憶體快照提供持久化能力
Redis穩定性之戰:AOF日誌支撐資料持久化

4 總結

擴充套件閱讀:快取雪崩、擊穿、穿透
架構與思維:一次快取雪崩的災難覆盤
架構與思維:再聊快取擊穿,面試是一場博弈

相關文章