項目中的緩存那些事兒

  1.爲什麼須要緩存?web

  在高併發請求時,爲什麼咱們頻繁提到緩存技術?最直接的緣由是,目前磁盤IO和網絡IO相對於內存IO的成百上千倍的性能劣勢。redis

  作個簡單計算,若是咱們須要某個數據,該數據從數據庫磁盤讀出來須要0.1s,從交換機傳過來須要0.05s,那麼每一個請求完成最少0.15s(固然,事實上磁盤和網絡IO也沒有這麼慢,這裏只是舉例),該數據庫服務器每秒只能響應67個請求;而若是該數據存在於本機內存裏,讀出來只須要10us,那麼每秒鐘可以響應100,000個請求。數據庫

  經過將高頻使用的數據存在離cpu更近的位置,以減小數據傳輸時間,從而提升處理效率,這就是緩存的意義。瀏覽器

  2.在哪裏用緩存?緩存

  一切地方。例如:服務器

  咱們從硬盤讀數據的時候,其實操做系統還額外把附近的數據都讀到了內存裏,例如,CPU在從內存裏讀數據的時候,也額外讀了許多數據到各級cache裏,各個輸入輸出之間用buffer保存一批數據統一發送和接受,而不是一個byte一個byte的處理,上面這是系統層面,在軟件系統設計層面,不少地方也用了緩存:網絡

  瀏覽器會緩存頁面的元素,這樣在重複訪問網頁時,就避開了要從互聯網上下載數據(例如大圖片)併發

  web服務會把靜態的東西提早部署在CDN上,這也是一種緩存app

  數據庫會緩存查詢,因此同一條查詢第二次就是要比第一次快分佈式

  內存數據庫(如redis)選擇把大量數據存在內存而非硬盤裏,這能夠看做是一個大型緩存,只是把整個數據庫緩存了起來

  應用程序把最近幾回計算的結果放在本地內存裏,若是下次到來的請求仍是原請求,就跳過計算直接返回結果

  3.常見事故分析

  一些常見的事故,系統又是怎麼設計的呢?

  通常底層是數據庫,中間放了一層redis,前面的業務系統所需的數據都直接從redis裏取,而後計算出結果返回給app;數據庫和redis的同步另外有程序保證,避免redis的穿透,防止了程序裏出現大量請求從redis裏找不到,因而又一窩蜂的去查數據庫,直接壓垮數據庫的狀況。從這個角度講,其實這一步是作的還能夠的。

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

  1.業務系統須要的數據雖然都在redis裏,可是是分開存放的。什麼意思呢,好比我前臺發起一個請求,後臺先去redis裏取一下標題,而後再取一下做者,而後再取一下內容,再取一下評論,再取一下轉發數等等……結果前臺一次請求,後臺要請求redis十幾回。高併發的時候,壓力一下被放大十幾倍,redis響應、網絡響應必然會變慢。

  2.其實作業務的那波人也意識到了這個狀況可能發生,因此作了熔斷機制,另起了一個緩存池,裏面放了一些備用數據,若是主業務超時,直接從緩存池裏取數據返回。可是他們設計的時候沒想周全,這個備選池的數據過時時間設計的太長了,裏面竟然還有好多天前更新進去的數據,最終致使了一大波用戶刷出來好久前的不可描述小視頻……

  說到這,不知道讀者有沒有意識到他們最致命的一個問題:這個業務系統徹底沒有考慮本地緩存(也就是在業務服務器內存裏作緩存)。好比像咱們這種app,一旦大量用戶同一時間涌進來,一定都是奔着少數幾個內容去的,這種特別集中的高頻次極少許數據訪問,又不須要對每一個用戶作特化的,簡直就是在臉上寫上「請緩存我」。

  這時候,若是能在業務端作一層本地緩存,直接把算好的數據本地存一份,那麼就會極大減小網絡和redis的壓力,不至於當場觸發熔斷了。

  4.淺談緩存的那些坑

  緩存頗有用,可是緩存用很差也會埋不少坑:

  緩存穿透

  緩存穿透是說收到了一個請求,可是該請求緩存裏沒有,只能去數據庫裏查詢,而後放進緩存。這裏面有兩個風險,一個是同時有好多請求訪問同一個數據,而後業務系統把這些請求全發到了數據庫;第二個是有人惡意構造一個邏輯上不存在的數據,而後大量發送這個請求,這樣每次請求都會被髮送到數據庫,可能致使數據掛掉。

  怎麼應對這種狀況呢?對於惡意訪問,一個思路是事先作校驗,對惡意數據直接過濾掉,不要發到數據庫層;第二個思路是緩存空結果,就是對查詢不存在的數據仍然記錄一條該數據不存在在緩存裏,這樣能有效的減小查詢數據庫的次數。

  那麼非惡意訪問呢?這個要結合緩存擊穿來說。

  緩存擊穿

  上面提到的某個數據沒有,而後好多請求都被髮到數據庫其實能夠歸爲緩存擊穿的範疇:對於熱點數據,當數據失效的一瞬間,全部請求都被下放到數據庫去請求更新緩存,數據庫被壓垮。

  怎麼防範這種問題呢?一個思路是全局鎖,就是全部訪問某個數據的請求都共享一個鎖,得到鎖的那個纔有資格去訪問數據庫,其餘線程必須等待。可是如今的業務都是分佈式的,本地鎖無法控制其餘服務器也等待,因此要用到全局鎖,好比用redis的setnx實現全局鎖。

  另外一個思路是對即將過時的數據主動刷新,作法能夠有不少,好比起一個線程輪詢數據,好比把全部數據劃分爲不一樣的緩存區間,按期分區間刷新數據等等。這第二個思路又和咱們接下來要講的緩存雪崩有關係。

  緩存雪崩

  緩存雪崩是指好比咱們給全部的數據設置了一樣的過時時間,而後在某一個歷史性時刻,整個緩存的數據所有過時了,而後瞬間全部的請求都被打到了數據庫,數據庫就崩了。

  解決思路要麼是分治,劃分更小的緩存區間,按區間過時;要麼是給每一個key的過時時間加個隨機值,避免同時過時,達到錯峯刷新緩存的目的。

  緩存刷新

  說到刷新緩存,其實也有坑的。好比我以前的一份工做裏,有一次大活動,正是如火如荼的時候,全部的廣告位忽然都變空白了。後來追查緣由,全部的廣告素材都在緩存裏,而後起了個程序,專門負責刷新緩存,每次把當前的素材全量刷新。

  壞就壞在這個全量上。由於大活動的時候流量極大,廣告更新壓力也很大,把負責提供更新素材的程序壓崩了。刷新緩存的程序在請求時,收到了一個返回結果Null。接下來就喜聞樂見了,刷新程序根據這個null,清空了整個緩存,全部廣告素材都失效了。

  總之,想要作好高併發系統的緩存,就要考慮到各類邊角狀況,當心設計,任何細小的疏忽均可能致使系統崩潰。

相關文章
相關標籤/搜索