redis快取常見問題場景總結

KerryWu發表於2023-04-09

在使用redis快取時,我們大概都聽過快取擊穿、快取雪崩之類的場景和方案,這也是一般常見面試題的內容。但在這些年的實際開發中,的確親身經歷了這類場景之後,對這類場景和解決方案就更加深刻,這類再統一總結一下。

當然方案不是唯一的,後續如果我用到更好的方案,依然會基於本文件補充。

1. 快取擊穿

1.1. 定義

快取擊穿,也就是說當redis快取中有一個key是大量請求同時訪問的熱點資料,如果突然這個key時間到了,那麼大量的請求在快取中獲取不到該key,穿過快取直接來到資料庫導致資料庫崩潰,這樣因為單個key失效而穿過快取到資料庫稱為快取擊穿。

1.2. 方案

1.2.1. 方案一:主動更新key

這裡問題在於key是被動重新整理的,即key過期後自動刪除,當流量請求進來了才去讀db更新快取。如果場景允許,我們可以主動控制性的重新整理key。key依然可以設定過期時間,但我們可以透過後臺job等方式,從db拿最新的值更新key對應的value,更新的同時自然對key進行續期。

這樣以達到能定時更新redis值,又保證redis key永遠不失效,突發大流量也走不到db。

1.2.2. 方案二:更新快取加鎖

快取擊穿的場景在壓測中經常可見,當沒有快取預熱時,redis 快取key一開始不存在。假設jmeter 100個執行緒併發訪問時,都發現快取key不存在,100個請求都會去查詢db,然後更新redis值。

其實我們在發現key不存在之後,可以對於“查db更新redis快取”這個過程加上分散式鎖,保證只要有一個請求去讀db、更新快取。其他的請求在等待鎖釋放後,再去查下redis快取。此時快取有值就直接訪問快取。

2. 快取雪崩

2.1. 定義

當大量請求在訪問都會先從快取查詢,如果此時大部分快取同時過期失效,那麼這些請求都查詢不到快取,此時他們會全部將請求到資料庫,當請求數量足夠大時此時將會把資料庫壓垮,這就是快取雪崩。比如:在凌晨十二點搞促銷,大約有10000個使用者發起請求,此時快取過期,則這10000個請求直接打到資料庫上,把資料庫壓垮,即使重啟資料庫請求依然會打到資料庫上。

這裡和“快取擊穿”不同的地方在於:

  • 快取擊穿:單個key過期,單個key的大量請求進來。
  • 快取雪崩:多個key同時過期,多個key的大量請求進來。

2.2. 方案

2.2.1. 方案一:過期時間分散

問題在於key同時過期。首先,如果業務上key的生成時間不是同時的,那麼過期時間也就不是同時的,這類問題可以避免。

如果key的生成時間是同時的,例如:快取是透過job或其他觸發條件批次一起生成的,那麼在定義每個key的過期時間時,可以基於原定的時間再加上一個隨機時間段,以保障最終的過期時間不一致。

2.2.2. 方案二:叢集架構

redis的叢集架構中,可根據寫入命令按照不同slot,分配在不同master上。因為這些key是不同的,針對快取雪崩場景,寫入的請求就可以分配在不同master節點上,能緩解一部分壓力。

當然,效果有限,但至少比單體架構抗壓強。

2.2.3. 方案三:降級限流

可評估資料庫能接受的最大請求量,做限流。那麼被限住的請求就要做服務降級,可在佇列中等待非同步更新redis值。

3. 快取穿透

3.1. 定義

指當請求查詢快取和資料庫都不存在的資料時,先查詢快取為空,再查詢資料庫依然為空,向請求返回空,如果大量請求同時訪問這些不存在key那麼這些請求依然會造成壓垮資料庫的現象,這種通常是惡意查詢和被攻擊機率較大。

快取穿透快取擊穿 名字聽起來很像,但不是一回事。快取穿透 是針對快取中不存在的key。而 快取擊穿 是原本存在某個快取key,等失效後突然大批次訪問這個key。

3.2. 方案

3.2.1. 快取null值

當訪問一些不存在的key時,因為在db中查到的值為null,就不會快取下來,所以下次訪問依然會走db。

那麼當在db中查到的值為null時,我們乾脆就建立一個快取key,存的值就為null等。當下次訪問的時候,就會走快取中取,而不用走db。

但是這個方案有侷限性,得看具體場景。假設第一次訪問的時候不存在,我們快取了一個null的key,很快db中對應的key就有值了,可我們訪問時依然是從快取中獲取了null。

有兩種改進策略:

  1. 可以針對null的值,我們的過期時間可以設短一點。(下策)
  2. 當db中值新建、更新時,能夠主動清除對應的快取key。(上策)

3.2.2. 布隆過濾器

詳見 《布隆過濾器 與 Redis BitMap》

4. 熱點key(快取擊穿)

4.1. 定義

這裡 (快取擊穿),是因為和快取擊穿場景有點像。前面說的快取擊穿,單指key過期,大流量擊穿db。而這裡不用等到key過期,直接更大的流量,擊穿redis,再擊穿db。

熱點key,就是瞬間有大量的請求去訪問redis上某個固定的key。例如一些熱搜詞條:IG奪冠、梅西奪冠,一瞬間會有大量的使用者請求都訪問固定的詞條,如果這些詞條內容存在redis中,那麼訪問的就是某個固定的key。

我們知道,就算是redis的叢集機構,針對某個固定的key,也是被分配某個固定的雜湊槽上,對應redis某單個節點。而redis單個節點的效能有限,此時就容易被擊潰,帶來的危害有:

1. 流量集中,達到物理網路卡上限

當某一熱點Key的請求在某一節點所在的主機上超過該主機網路卡流量上限時,由於流量的過度集中,會導致該節點的伺服器中其它服務無法進行

2. 請求過多,快取分片服務被打垮

Redis單點查詢效能是有限的,單節點QPS差不多也就幾萬,當熱點key的查詢超過Redis節點的效能閾值時,請求會佔用大量的CPU資源,影響其他請求並導致整體效能降低;嚴重時會導致快取分片服務被打垮,表現形式之一就是Redis節點自重啟,此時該節點儲存的所有key的查詢都是不可用狀態,會把影響輻射到其他業務上。

3. 叢集架構下,產生訪問傾斜

即某個資料分片被大量訪問,而其他資料分片處於空閒狀態,可能引起該資料分片的連線數被耗盡,新的連線建立請求被拒絕等問題。

4. DB 擊穿,引起業務雪崩

熱Key的請求壓力數量超出Redis的承受能力易造成快取擊穿,當快取掛掉時,此時再有請求產生,可能直接把大量請求直接打到DB層上,由於DB層相對快取層查詢效能更弱,在面臨大請求時很容易發生DB雪崩現象,嚴重影響業務。

4.2. 識別熱點key

4.2.1. 業務經驗評估

比如熱搜關鍵詞、秒殺商品的詞條等等,能夠提前識別到可能是熱點key的,就提前做準備。

4.2.2. 業務側監控

在操作redis之前,加入一行程式碼進行資料統計,非同步上報行為;如類似日誌採集,將單次redis命令的操作/結果/耗時等統計,非同步訊息傳送給採集訊息佇列,缺點就是對程式碼造成入侵,一般可以交給中介軟體加在自己包的redis二方包中;如果有做的好一點的Daas平臺,可以在proxy層做監控,業務無需感知,統一在Daas平臺檢視redis監控。

4.2.3. redis伺服器端監控

redis自帶一些監控命令,可由運維側基於redis的命令,提供一些監控平臺。

1. monitor命令

該命令可以實時抓取出redis伺服器接收到的命令,然後寫程式碼統計出熱key是啥;當然,也有現成的分析工具可以給你使用,比如redis-faina;但是該命令在高併發的條件下,有記憶體增暴增的隱患,還會降低redis的效能。

2. hotkeys引數

redis 4.0.3提供了redis-cli的熱點key發現功能,執行redis-cli時加上–hotkeys選項即可;但是該引數在執行的時候,如果key比較多,執行起來比較慢。

4.3. 方案

4.3.1. 二級快取

針對熱點key做二級快取,將流量分攤到java各個節點的記憶體中,也減少了網路頻寬。

當然,二級快取帶來的缺點就是難維護快取的一致性。不過這也算是針對熱點key場景下的一種降級策略。

4.3.2. key副本拆分

可以將某個key的內容複製成多個key,如:xxkey_01、xxkey_02、xxkey_03...,這樣這些key就會分散在不同的雜湊槽中,不同的redis節點上。

在java程式碼每次請求redis key時,可以透過輪詢等方式,訪問不同key中的資料。就將流量平攤到不同的redis節點中。

前面是設定編號,訪問時隨機訪問其中一個key。還可以考慮按照使用者群體、訪問場景等維度拆分,拆分生成不同的key,同樣的思路,也是可以將流量拆分開。

4.3.3. 熱點key redis叢集隔離

如果財力允許,為了防止熱點key引發問題時,核心業務不受影響,應當提前做好核心/非核心業務的Redis的隔離,至少熱點key存在的redis叢集應當與核心業務隔離開來。

4.4. 有贊TMC框架實行

有贊專門設計了一套框架解決熱點key的問題,具體內容請看原文 《有贊透明多級快取解決方案(TMC)設計思路》。下面做簡單說明。

TMC 本地快取整體結構分為如下模組:

  • Jedis-Client:Java 應用與快取服務端互動的直接入口,介面定義與原生 Jedis-Client 無異;
  • Hermes-SDK:自研“熱點發現+本地快取”功能的 SDK 封裝,Jedis-Client 透過與它互動來整合相應能力;
  • Hermes 服務端叢集:接收 Hermes-SDK 上報的快取訪問資料,進行熱點探測,將熱點 key 推送給 Hermes-SDK 做本地快取;
  • 快取叢集:由代理層和儲存層組成,為應用客戶端提供統一的分散式快取服務入口;
  • 基礎元件:etcd 叢集、Apollo 配置中心,為 TMC 提供“叢集推送”和“統一配置”能力;

1. 監控

有贊改寫了jedis原生的jar包,加入了Hermes-SDK包,目的就是做熱點發現和本地快取;
從監控的角度看,該包對於Jedis-Client的每次key值訪問請求,Hermes-SDK 都會透過其通訊模組將key訪問事件非同步上報給Hermes服務端叢集,以便其根據上報資料進行“熱點探測”。

2. 熱key處理方案

在處理熱key方案上,有贊用的是二級快取。

有贊在監控到熱key後,Hermes服務端叢集會透過各種手段通知各業務系統裡的Hermes-SDK,告訴他們這個key是熱key,要做本地快取。 於是Hermes-SDK就會將該key快取在本地,對於後面的請求;Hermes-SDK發現這個是一個熱key,直接從本地中拿,而不會去訪問叢集;

3. 二級快取一致性方案

Hermes-SDK的熱點模組僅快取熱點key資料,絕大多數非熱點key資料由快取叢集儲存;

熱點key變更導致value失效時,Hermes-SDK 透過etcd叢集廣播事件,非同步失效業務應用叢集中其他節點的本地快取,保證叢集最終一致。

相關文章