三大問題!Redis緩存異常及處理方案總結(jié)
Redis作為一個高性能的內(nèi)存中的key-value數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),在我們?nèi)粘i_發(fā)中廣泛應用于緩存、計數(shù)器、消息隊列、排行榜等場景中,尤其是作為最常用的緩存方式,在提高數(shù)據(jù)查詢效率、保護數(shù)據(jù)庫等方面起到了不可磨滅的作用,但實際應用中,可能會出現(xiàn)一些Redis緩存異常的情況,本文主要對Redis緩存異常及處理方案進行了總結(jié)。
1. 背景
Redis是一個完全開源的、遵守BSD協(xié)議的、高性能的key-value數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),它支持數(shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保存在磁盤中,而且不僅僅支持簡單的key-value類型的數(shù)據(jù),同時還提供list,set,zset,hash等數(shù)據(jù)結(jié)構(gòu)的存儲,功能十分強大,Redis還支持數(shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份,從而提高可用性。當然最重要的還是讀寫速度快,作為我們平常開發(fā)中最常用的緩存方案被廣泛應用。但在實際應用過程中,它會存在緩存雪崩、緩存擊穿和緩存穿透等異常情況,如果忽視這些情況可能會帶來災難性的后果,下面主要對這些緩存異常和常見處理方案進行相應分析與總結(jié)。
2. 緩存雪崩
(1) 是什么
一段時間內(nèi)本應在redis緩存中處理的大量請求,都發(fā)送到了數(shù)據(jù)庫進行處理,導致對數(shù)據(jù)庫的壓力迅速增大,嚴重時甚至可能導致數(shù)據(jù)庫崩潰,從而導致整個系統(tǒng)崩潰,就像雪崩一樣,引發(fā)連鎖效應,所以叫緩存雪崩。
(2) 為什么
出現(xiàn)上述情況的常見原因主要有以下兩點:
- 大量緩存數(shù)據(jù)同時過期,導致本應請求到緩存的需重新從數(shù)據(jù)庫中獲取數(shù)據(jù)。
- redis本身出現(xiàn)故障,無法處理請求,那自然會再請求到數(shù)據(jù)庫那里。
(3) 怎么辦
針對大量緩存數(shù)據(jù)同時過期的情況:
- 實際設置過期時間時,應當盡量避免大量key同時過期的場景,如果真的有,那就通過隨機、微調(diào)、均勻設置等方式設置過期時間,從而避免同一時間過期。
- 添加互斥鎖,使得構(gòu)建緩存的操作不會在同一時間進行。
- 雙key策略,主key是原始緩存,備key為拷貝緩存,主key失效時,可以訪問備key,主key緩存失效時間設置為短期,備key設置為長期。
- 后臺更新緩存策略,采用定時任務或者消息隊列的方式進行redis緩存更新或移除等。
針對redis本身出現(xiàn)故障的情況:
- 在預防層面,可以通過主從節(jié)點的方式構(gòu)建高可用的集群,也就是實現(xiàn)主Redis實例掛掉后,能有其他從庫快速切換為主庫,繼續(xù)提供服務。
- 如果事情已經(jīng)發(fā)生了,那就要為了防止數(shù)據(jù)庫被大量的請求搞崩潰,可以采用服務熔斷或者請求限流的方法。當然服務熔斷相對粗暴一些,停止服務直到redis服務恢復,請求限流相對溫和一些,保證一些請求可以處理,不是一刀切,不過還是看具體業(yè)務情況選擇合適的處理方案。
3. 緩存擊穿
(1) 是什么
緩存擊穿一般出現(xiàn)在高并發(fā)系統(tǒng)中,是大量并發(fā)用戶同時請求到緩存中沒有但數(shù)據(jù)庫中有的數(shù)據(jù),也就是同時讀緩存沒讀到數(shù)據(jù),又同時去數(shù)據(jù)庫去取數(shù)據(jù),引起數(shù)據(jù)庫壓力瞬間增大。和緩存雪崩不同的是,緩存擊穿指并發(fā)查同一條數(shù)據(jù),緩存雪崩是不同數(shù)據(jù)都過期了,很多數(shù)據(jù)都查不到從而查數(shù)據(jù)庫。
(2) 為什么
這種情況其實一般出現(xiàn)的原因就是某個熱點數(shù)據(jù)緩存過期,由于是熱點數(shù)據(jù),請求并發(fā)量又大,所以過期的時候還是會有大量請求同時過來,來不及更新緩存就全部打到數(shù)據(jù)庫那邊了。
(3) 怎么辦
針對這種情況有兩種常見的處理方案:
- 簡單粗暴的對熱點數(shù)據(jù)不設置過期時間,這樣不會過期,自然也就不會出現(xiàn)上述情況了,如果后續(xù)想清理,可以通過后臺進行清理。
- 添加互斥鎖,即當過期之后,除了請求過來的第一個查詢的請求可以獲取到鎖請求到數(shù)據(jù)庫,并再次更新到緩存中,其他的會被阻塞住,直到鎖被釋放,同時新的緩存也被更新上去了,后續(xù)請求又會請求到緩存上,這樣就不會出現(xiàn)緩存擊穿了。
4. 緩存穿透
(1) 是什么
緩存穿透是指數(shù)據(jù)既不在redis中,也不在數(shù)據(jù)庫中,這樣就導致每次請求過來的時候,在緩存中找不到對應key之后,每次都還要去數(shù)據(jù)庫再查詢一遍,發(fā)現(xiàn)數(shù)據(jù)庫也沒有,相當于進行了兩次無用的查詢。這樣請求就可以繞過緩存直接查數(shù)據(jù)庫,如果這個時候有人想惡意攻擊系統(tǒng),就可以故意使用空值或者其他不存在的值進行頻繁請求,那么就會對數(shù)據(jù)庫造成比較大的壓力。
(2) 為什么
這種現(xiàn)象的原因其實很好理解,業(yè)務邏輯里面如果用戶對某些信息還沒有進行相應的操作或者處理,那對應的存放信息的數(shù)據(jù)庫或者緩存中自然也就沒有相應的數(shù)據(jù),也就容易出現(xiàn)上述問題。
(3) 怎么辦
針對緩存穿透,一般有以下三種處理方案:
- 非法請求的限制,主要是指參數(shù)校驗、鑒權(quán)校驗等,從而一開始就把大量的非法請求攔截在外,這在實際業(yè)務開發(fā)中是必要的手段。
- 緩存空值或者默認值,如果從緩存取不到的數(shù)據(jù),在數(shù)據(jù)庫中也沒有取到,那我們?nèi)匀话堰@個空結(jié)果進行緩存,同時設置一個較短的過期時間。通過這個設置的默認值存放到緩存,這樣第二次到緩存中獲取就有值了,而不會繼續(xù)訪問數(shù)據(jù)庫,可以防止有大量惡意請求是反復用同一個key進行攻擊。
- 使用布隆過濾器快速判斷數(shù)據(jù)是否存在。那什么是布隆過濾器呢,簡單來說,就是可以引入了多個相互獨立的哈希函數(shù),保證在給定的空間和誤判率下,完成元素判重。
因為我們知道,存在hash碰撞這樣一種情況,那如果只使用一個hash函數(shù),則碰撞沖突的概率明顯會變大,那為了減少這種沖突,我們可以多引入幾個hash函數(shù),而布隆過濾器算法的核心思想就是利用多個不同的hash函數(shù)來解決這樣一種沖突。它的優(yōu)點是空間效率高,查詢時間短,遠超其他算法,而它的缺點就是會存在一定的誤識別率,它不能完全保證請求過來的key,通過布隆過濾器的校驗,就一定有這個數(shù)據(jù),畢竟理論上還是會存在沖突情況,無論概率多小。
但是,只要沒有通過布隆過濾器的校驗,那么這個key就一定不存在,只要利用這一點其實就已經(jīng)可以過濾掉大部分不存在的key的請求了,在正常場景下已然足夠了。
5. 其他
除了上述三種常見的Redis緩存異常問題之外,還經(jīng)常聽到的有緩存預熱和緩存降級兩個名詞,與其說是異常問題,不如說是兩種的優(yōu)化處理方法。
(1) 緩存預熱
緩存預熱就是系統(tǒng)上線前后,將相關的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)中去,而不依賴用戶。這樣就可以避免在用戶請求的時候,先查詢數(shù)據(jù)庫,然后再將數(shù)據(jù)緩存的問題。用戶直接查詢事先被預熱的緩存數(shù)據(jù),這樣可以避免那么系統(tǒng)上線初期,對于高并發(fā)的流量,都會訪問到數(shù)據(jù)庫中, 對數(shù)據(jù)庫造成流量的壓力。根據(jù)數(shù)據(jù)不同量級,可以有以下幾種做法:
- 數(shù)據(jù)量不大:項目啟動的時候自動進行加載。
- 數(shù)據(jù)量較大:后臺定時刷新緩存。
- 數(shù)據(jù)量極大:只針對熱點數(shù)據(jù)進行預加載緩存操作。
(2) 緩存降級
緩存降級是指當緩存失效或緩存服務出現(xiàn)問題時,為了防止緩存服務故障,導致數(shù)據(jù)庫跟著一起發(fā)生雪崩問題,所以也不去訪問數(shù)據(jù)庫,但因為一些原因,仍然想要保證服務還是基本可用的,雖然肯定會是有損服務。因此,對于不重要的緩存數(shù)據(jù),我們可以采取服務降級策略。一般做法有以下兩種:
- 直接訪問內(nèi)存部分的數(shù)據(jù)緩存。
- 直接返回系統(tǒng)設置的默認值。
6. 總結(jié)
本文主要對常見的Redis緩存異常及其處理方案進行了總結(jié),可以用下圖做個概括: