三分鐘帶你掌握緩存穿透、緩存雪崩、緩存擊穿,以及應對方案!
?一、介紹
每場后端面試,似乎都少不了關于 redis 的話題,比如項目使用過哪些分布式緩存服務,為什么要使用 redis,有沒有碰到過緩存失效、緩存穿透、緩存雪崩等問題。
在前幾篇關于 redis 的介紹文章中,我們說到項目中之所以會引入分布式緩存服務,主要是為了解決集群環(huán)境下,內存數據不共享的問題,比如 session 會話,以及一些字典緩存等等,在當前服務器的內存中存儲,在另一臺服務器中難以獲取查詢的問題,通過引入緩存服務,將緩存數據統(tǒng)一歸一到一個服務器里面,以解決系統(tǒng)中內存數據不共享的問題,同時緩存性能也不會受到很大影響。
當然軟件開源市場上,也有很多的分布式緩存服務,比如比較有名的有 redis、memcached 等,相對比 memcached,redis 各項指標都要比 memcached 強很多,Redis 號稱能讀的速度是 110000 次/s,寫的速度是 81000次/s,無數的實踐證明 redis 確實是當前一款非常高性能的內存數據庫。
站在面試官的角度,軟件系統(tǒng)的技術選型以及以上的相關技術問題,在實際的生產環(huán)境中確實也會發(fā)生,通過以此話題為切入點,可以更加清晰的了解面試者是否也碰到過類似的問題,以及對應處理的辦法。
那么站在面試者的角度,除了熟練的掌握 redis 的使用方法以外,我們可能還需要更加深入的了解如果引入 redis 之后,系統(tǒng)中可能會發(fā)生的一些問題以及應對辦法。
今天我們一起聊聊吧。
二、常見問題
2.1、問題一:為什么存入 redis 的數據,查詢失效
Redis 的所有數據都是保存在內存中,然后不定期的通過異步方式保存到磁盤上;也可以把每一次數據變化都寫入到一個aof日志文件里面,當 redis 的服務器重啟的時候,自動從日志文件里面恢復數據到內存中。
有哪些場景會發(fā)生緩存失效呢?總結起來有以下兩種場景:
- 當 redis 服務器重啟的時候,可能會發(fā)生緩存失效,此時可以將 redis 的持久化方式改成AOF模式,也就是全持久化模式,但是性能會消耗很大
- 存入redis 的數據,設置了自動過期時間,這種情況可以重新調整過期時間
2.2、問題二:緩存與數據庫的數據不一致
通常情況下我們使用緩存,其中有一個很重要的目的就是降低數據庫的訪問壓力,比如商品的信息查詢,優(yōu)先是從緩存中查詢,如果沒有,再從數據庫里面查詢。
對于既有數據庫寫入又有緩存操作的接口,一般分為兩種情況執(zhí)行。
- 先寫入數據庫,再操作緩存。這種情況下如果數據庫操作成功,緩存操作失敗就會導致緩存和數據庫不一致
- 先操作緩存,再寫入數據庫。這種情況下如果緩存操作成功,數據庫操作失敗也會導致數據庫和緩存不一致
大部分情況下,緩存理論上都是需要可以從數據庫恢復出來的,所以基本上采取第一種順序都是不會有問題的,但是無法保證數據庫和緩存完全一致。
也就是說,使用緩存,就可能會出現緩存與數據庫不一致的情況,只是說這種幾幾率的情況有多大。
針對那些必須保證數據庫和緩存一致的情況,通常是不建議使用緩存的,直接從數據庫查詢。
2.3、問題三:什么是緩存穿透
緩存穿透,表示惡意用戶頻繁的模擬請求緩存中不存在的數據,此時如果有大量的接口請求,短時間內會直接落在了數據庫上,緩存被擊穿,導致數據庫性能急劇下降,最終影響服務整體的性能。
這個在實際項目中很容易遇到,如搶購活動、秒殺活動、搶優(yōu)惠券等接口 API 被大量的惡意用戶刷,導致短時間內數據庫宕機。對于緩存擊穿的問題,有以下幾種解決方案。
- 使用分布式鎖排隊。當從緩存中獲取數據失敗時,給當前接口加上鎖,從數據庫中加載完數據并寫入后再釋放鎖。若其它線程獲取鎖失敗,則等待一段時間后再重試。
- 使用布隆過濾器。將所有可能存在的數據哈希到一個足夠大的bitmap?中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力
- 對空結果進行緩存。如果一個查詢返回的數據為空,我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘,這樣第二次到緩存中獲取就有值了,而不會繼續(xù)訪問數據庫,簡單粗暴好使。
2.4、問題四:什么是緩存雪崩
緩存雪崩,簡單的說就是在短時間內有大量緩存失效,如果這期間有大量的請求發(fā)生,同樣也有可能會導致數據庫發(fā)生宕機。在 Redis 機群的數據分布算法上如果使用的是傳統(tǒng)的 hash 取模算法,在增加或者移除 Redis 節(jié)點的時候就會出現大量的緩存臨時失效的情形。
對于緩存雪崩的問題,有以下幾種解決方案。
- 像解決緩存穿透一樣加鎖排隊
- 建立備份緩存。比如緩存 A 和緩存 B,A 設置超時時間,B 不設值超時時間,先從 A 讀緩存,A 沒有讀 B,當緩存 A 發(fā)生變化的時候,同時更新緩存 B
- 計算數據緩存節(jié)點的時候采用一致性 hash 算法,這樣在節(jié)點數量發(fā)生改變時不會存在大量的緩存數據需要遷移的情況發(fā)生
2.5、問題五:redis 緩存會不會出現并發(fā)問題
首先 Redis 是單線程執(zhí)行命令的,在出現多個 Redis Client 并發(fā)操作數據時,秉承先發(fā)起先執(zhí)行的原則,其它的處于阻塞狀態(tài)。
redis 緩存并發(fā)問題,其實主要指的還是讀取數據庫數據的并發(fā)操作問題。
當緩存過期后會從數據庫查詢數據然后再存入Redis?緩存,但是在高并發(fā)情況下,可能還沒來得及將數據庫中查出來的數據存入Redis?時,其它Client?又從數據庫里查詢數據再存入Redis了。
這樣一來會造成多個請求并發(fā)的從數據庫獲取數據,然后存入Redis,可能在讀取的時候,出現臟數據。
針對這種場景,有以下幾種解決方案。
- 同步加鎖處理。在寫入數據庫的時候,再操作緩存這個階段,進行加鎖處理,保證服務串行,可能會犧牲一點時間
- 異步隊列串行執(zhí)行。把寫入數據庫和操作緩存的操作,放在隊列中使其串行化,讓他們一個一個的執(zhí)行,比如通過消息中間件異步執(zhí)行。
- 使用類似SQL的樂觀鎖機制:在并發(fā)寫入Redis?緩存時,把要寫入數據的版本號和時間戳與Redis?中的數據進行對比,如果寫入的數據時間戳或者版本號 比Redis高,則寫入;否則就不寫入
三、小結
本文主要圍繞 redis 使用中出現的一些場景問題,進行一次簡單的總結,如果有疏漏的地方,歡迎網友留言指出!
四、參考
1、博客園 - 卡斯特梅的雨傘- springboot中RedisTemplate的使用