天天看點

Redis高并發請求的緩存設計政策

高并發請求的緩存設計政策

前幾天,我司出了個簍子。當時正值某喜聞樂見的關鍵比賽結束,一堆人打開我司app準備看點東西,結果從來沒有感受到過這麼多關注量的該功能瞬間幸福到眩暈,觸發了熔斷,結果就是大量興緻沖沖打開app準備看該比賽結果的人被迫刷了十分鐘三天前的野外跑酷,負責内容的人火大到直接罵娘。

雖然這個業務不是我負責,但是也跟相關的人聊了下情況,感慨了一下,于是有了這一篇文章。

1.為何需要緩存?

在高并發請求時,為何我們頻繁提到緩存技術?最直接的原因是,目前磁盤IO和網絡IO相對于記憶體IO的成百上千倍的性能劣勢。

做個簡單計算,如果我們需要某個資料,該資料從資料庫磁盤讀出來需要0.1s,從交換機傳過來需要0.05s,那麼每個請求完成最少0.15s(當然,事實上磁盤和網絡IO也沒有這麼慢,這裡隻是舉例),該資料庫伺服器每秒隻能響應67個請求;而如果該資料存在于本機記憶體裡,讀出來隻需要10us,那麼每秒鐘能夠響應100,000個請求。

通過将高頻使用的資料存在離cpu更近的位置,以減少資料傳輸時間,進而提高處理效率,這就是緩存的意義。

2.在哪裡用緩存?

一切地方。例如:

  • 我們從硬碟讀資料的時候,其實作業系統還額外把附近的資料都讀到了記憶體裡
  • 例如,CPU在從記憶體裡讀資料的時候,也額外讀了許多資料到各級cache裡
  • 各個輸入輸出之間用buffer儲存一批資料統一發送和接受,而不是一個byte一個byte的處理

上面這是系統層面,在軟體系統設計層面,很多地方也用了緩存:

  • 浏覽器會緩存頁面的元素,這樣在重複通路網頁時,就避開了要從網際網路上下載下傳資料(例如大圖檔)
  • web服務會把靜态的東西提前部署在CDN上,這也是一種緩存
  • 資料庫會緩存查詢,是以同一條查詢第二次就是要比第一次快
  • 記憶體資料庫(如redis)選擇把大量資料存在記憶體而非硬碟裡,這可以看作是一個大型緩存,隻是把整個資料庫緩存了起來
  • 應用程式把最近幾次計算的結果放在本地記憶體裡,如果下次到來的請求還是原請求,就跳過計算直接傳回結果

3.本次事故分析

回到本文開始的問題上,該系統是怎麼設計的呢?底層是資料庫,中間放了一層redis,前面的業務系統所需的資料都直接從redis裡取,然後計算出結果傳回給app;資料庫和redis的同步另外有程式保證,避免redis的穿透,防止了程式裡出現大量請求從redis裡找不到,于是又一窩蜂的去查資料庫,直接壓垮資料庫的情況。從這個角度講,其實這一步是做的還可以的。

但是這個系統有兩個問題:

1.業務系統需要的資料雖然都在redis裡,但是是分開存放的。什麼意思呢,比如我前台發起一個請求,背景先去redis裡取一下标題,然後再取一下作者,然後再取一下内容,再取一下評論,再取一下轉發數等等……結果前台一次請求,背景要請求redis十幾次。高并發的時候,壓力一下被放大十幾倍,redis響應、網絡響應必然會變慢。

2.其實做業務的那波人也意識到了這個情況可能發生,是以做了熔斷機制,另起了一個緩存池,裡面放了一些備用資料,如果主業務逾時,直接從緩存池裡取資料傳回。但是他們設計的時候沒想周全,這個備選池的資料過期時間設計的太長了,裡面居然還有三天前更新進去的資料,最終導緻了一大波使用者刷出來三天前的野外生态小視訊……

說到這,不知道讀者有沒有意識到他們最緻命的一個問題:這個業務系統完全沒有考慮本地緩存(也就是在業務伺服器記憶體裡做緩存)。比如像我們這種app,一旦大量使用者同一時間湧進來,必定都是奔着少數幾個内容去的,這種特别集中的高頻次極少量資料通路,又不需要對每個使用者做特化的,簡直就是在臉上寫上“請緩存我”。

這時候,如果能在業務端做一層本地緩存,直接把算好的資料本地存一份,那麼就會極大減少網絡和redis的壓力,不至于當場觸發熔斷了。

版權屬于:dingzhenhua

本文連結:https://www.dcmickey.cn/skill/33.html

轉載時須注明出處及本聲明