前幾天,我司出了個簍子。當時正值某喜聞樂見的關鍵比賽結束,一堆人打開我司app準備看點東西,結果歷來沒有感覺到過這麼多關注量的該功能瞬間幸福到眩暈,觸發了熔斷,結果就是大量興致沖沖打開app準備看該比賽結果的人被迫刷了十分鐘三天前的野外跑酷,負責內容的人火大到直接罵娘。html
雖然這個業務不是我負責,可是也跟相關的人聊了下狀況,感慨了一下,因而有了這一篇文章。web
在高併發請求時,爲什麼咱們頻繁提到緩存技術?最直接的緣由是,目前磁盤IO和網絡IO相對於內存IO的成百上千倍的性能劣勢。
作個簡單計算,若是咱們須要某個數據,該數據從數據庫磁盤讀出來須要0.1s,從交換機傳過來須要0.05s,那麼每一個請求完成最少0.15s(固然,事實上磁盤和網絡IO也沒有這麼慢,這裏只是舉例),該數據庫服務器每秒只能響應67個請求;而若是該數據存在於本機內存裏,讀出來只須要10us,那麼每秒鐘可以響應100,000個請求。redis
經過將高頻使用的數據存在離cpu更近的位置,以減小數據傳輸時間,從而提升處理效率,這就是緩存的意義。數據庫
一切地方。例如:瀏覽器
上面這是系統層面,在軟件系統設計層面,不少地方也用了緩存:緩存
回到本文開始的問題上,該系統是怎麼設計的呢?底層是數據庫,中間放了一層redis,前面的業務系統所需的數據都直接從redis裏取,而後計算出結果返回給app;數據庫和redis的同步另外有程序保證,避免redis的穿透,防止了程序裏出現大量請求從redis裏找不到,因而又一窩蜂的去查數據庫,直接壓垮數據庫的狀況。從這個角度講,其實這一步是作的還能夠的。服務器
可是這個系統有兩個問題:
1.業務系統須要的數據雖然都在redis裏,可是是分開存放的。什麼意思呢,好比我前臺發起一個請求,後臺先去redis裏取一下標題,而後再取一下做者,而後再取一下內容,再取一下評論,再取一下轉發數等等……結果前臺一次請求,後臺要請求redis十幾回。高併發的時候,壓力一下被放大十幾倍,redis響應、網絡響應必然會變慢。
2.其實作業務的那波人也意識到了這個狀況可能發生,因此作了熔斷機制,另起了一個緩存池,裏面放了一些備用數據,若是主業務超時,直接從緩存池裏取數據返回。可是他們設計的時候沒想周全,這個備選池的數據過時時間設計的太長了,裏面竟然還有三天前更新進去的數據,最終致使了一大波用戶刷出來三天前的野外生態小視頻……網絡
說到這,不知道讀者有沒有意識到他們最致命的一個問題:這個業務系統徹底沒有考慮本地緩存(也就是在業務服務器內存裏作緩存)。好比像咱們這種app,一旦大量用戶同一時間涌進來,一定都是奔着少數幾個內容去的,這種特別集中的高頻次極少許數據訪問,又不須要對每一個用戶作特化的,簡直就是在臉上寫上「請緩存我」。
這時候,若是能在業務端作一層本地緩存,直接把算好的數據本地存一份,那麼就會極大減小網絡和redis的壓力,不至於當場觸發熔斷了。併發
緩存頗有用,可是緩存用很差也會埋不少坑:app
緩存穿透是說收到了一個請求,可是該請求緩存裏沒有,只能去數據庫裏查詢,而後放進緩存。這裏面有兩個風險,一個是同時有好多請求訪問同一個數據,而後業務系統把這些請求全發到了數據庫;第二個是有人惡意構造一個邏輯上不存在的數據,而後大量發送這個請求,這樣每次請求都會被髮送到數據庫,可能致使數據掛掉。
怎麼應對這種狀況呢?對於惡意訪問,一個思路是事先作校驗,對惡意數據直接過濾掉,不要發到數據庫層;第二個思路是緩存空結果,就是對查詢不存在的數據仍然記錄一條該數據不存在在緩存裏,這樣能有效的減小查詢數據庫的次數。
那麼非惡意訪問呢?這個要結合緩存擊穿來說。
上面提到的某個數據沒有,而後好多請求都被髮到數據庫其實能夠歸爲緩存擊穿的範疇:對於熱點數據,當數據失效的一瞬間,全部請求都被下放到數據庫去請求更新緩存,數據庫被壓垮。
怎麼防範這種問題呢?一個思路是全局鎖,就是全部訪問某個數據的請求都共享一個鎖,得到鎖的那個纔有資格去訪問數據庫,其餘線程必須等待。可是如今的業務都是分佈式的,本地鎖無法控制其餘服務器也等待,因此要用到全局鎖,好比用redis的setnx實現全局鎖。
另外一個思路是對即將過時的數據主動刷新,作法能夠有不少,好比起一個線程輪詢數據,好比把全部數據劃分爲不一樣的緩存區間,按期分區間刷新數據等等。這第二個思路又和咱們接下來要講的緩存雪崩有關係。
緩存雪崩是指好比咱們給全部的數據設置了一樣的過時時間,而後在某一個歷史性時刻,整個緩存的數據所有過時了,而後瞬間全部的請求都被打到了數據庫,數據庫就崩了。
解決思路要麼是分治,劃分更小的緩存區間,按區間過時;要麼是給每一個key的過時時間加個隨機值,避免同時過時,達到錯峯刷新緩存的目的。
說到刷新緩存,其實也有坑的。好比我以前的一份工做裏,有一次大活動,正是如火如荼的時候,全部的廣告位忽然都變空白了。後來追查緣由,全部的廣告素材都在緩存裏,而後起了個程序,專門負責刷新緩存,每次把當前的素材全量刷新。
壞就壞在這個全量上。由於大活動的時候流量極大,廣告更新壓力也很大,把負責提供更新素材的程序壓崩了。刷新緩存的程序在請求時,收到了一個返回結果Null
。接下來就喜聞樂見了,刷新程序根據這個null,清空了整個緩存,全部廣告素材都失效了。
總之,想要作好高併發系統的緩存,就要考慮到各類邊角狀況,當心設計,任何細小的疏忽均可能致使系統崩潰。
原文:https://www.cnblogs.com/bethunebtj/p/9159914.html