文章原創於公眾號:程式猿周先森。本平臺不定時更新,喜歡我的文章,歡迎關注我的微信公眾號。
上篇文章談到了Redis分散式鎖,實際上就是為了解釋為什麼做快取採用Redis而不使用map/guava。快取分為本地快取和分散式快取。以 Java 為例,使用自帶的 map /guava 實現的是本地快取,最主要的特點是輕量以及快速,生命週期隨著 JVM 的銷燬而結束。而且在多例項狀態下快取不具有唯一性。使用 Redis 作快取稱為分散式快取,在多例項狀態下共用一份快取資料,快取具有一致性。所以說在分散式下最合適的快取方案就是採用Redis實現分散式快取。
本篇文章主要談談Redis中很容易出現的三大問題現象:快取擊穿、快取穿透以及快取雪崩。不過在介紹這三個問題現象之前,我們首先需要先來了解下Redis中key的過期淘汰機制。眾所周知,Redis可以對儲存在Redis中的快取資料設定過期時間,比如我們獲取的簡訊驗證碼一般十分鐘過期,我們這時候就需要在驗證碼存進Redis時新增一個key的過期時間,但是這裡有一個需要格外注意的問題就是:並非key過期時間到了就一定會被Redis給刪除。那麼Redis是如何做到對過期key進行刪除呢?Redis中對於過期key的刪除分為兩種策略:定期刪除和惰性刪除。
- 定期刪除:Redis 預設是每隔 100ms 就隨機抽取一些設定了過期時間的 Key,檢查其是否過期,如果過期就刪除。為什麼是隨機抽取而不是檢查所有key?因為你如果設定的key成千上萬,每100毫秒都將所有存在的key檢查一遍,會給CPU帶來比較大的壓力。
- 惰性刪除 :定期刪除由於是隨機抽取可能會導致很多過期 Key 到了過期時間並沒有被刪除。所以使用者在從快取獲取資料的時候,redis會檢查這個key是否過期了,如果過期就刪除這個key。這時候就會在查詢的時候將過期key從快取中清除。
但是如果僅僅使用定期刪除 + 惰性刪除機制還是會留下一個嚴重的隱患:如果定期刪除留下了很多已經過期的key,而且使用者長時間都沒有使用過這些過期key,導致過期key無法被惰性刪除,從而導致過期key一直堆積在記憶體裡,最終造成Redis記憶體塊被消耗殆盡。那這個問題如何解決呢?這個時候Redis記憶體淘汰機制應運而生了。Redis記憶體淘汰機制提供了6種資料淘汰策略:
- volatile-lru:從已設定過期時間的資料集中挑選最近最少使用的資料淘汰。
- volatile-ttl:從已設定過期時間的資料集中挑選將要過期的資料淘汰。
- volatile-random:從已設定過期時間的資料集中任意選擇資料淘汰。
- allkeys-lru:當記憶體不足以容納新寫入資料時移除最近最少使用的key。
- allkeys-random:從資料集中任意選擇資料淘汰。
- no-enviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯。
一般情況下,推薦使用volatile-lru策略,對於配置資訊等重要資料,不應該設定過期時間,這樣Redis就永遠不會淘汰這些重要資料。對於一般資料可以新增一個快取時間,當資料失效則請求會從DB中獲取並重新存入Redis中。
快取擊穿講完了Redis的key的過期淘汰機制,接下我們可以進入正題:為什麼會出現快取擊穿、快取穿透和快取雪崩現象呢?首先我們來看下請求是如何取到資料的:當接收到使用者請求,首先先嚐試從Redis快取中獲取到資料,如果快取中能取到資料則直接返回結果,當快取中不存在資料時從DB獲取資料,如果資料庫成功取到資料,則更新Redis,然後返回資料,如果DB無資料,則返回空結果。那什麼情況下會出現三大問題現象呢?我們先來看下快取擊穿的情況:定義:高併發的情況下,某個熱門key突然過期,導致大量請求在Redis未找到快取資料,進而全部去訪問DB請求資料,引起DB壓力瞬間增大。
解決方案:快取擊穿的情況下一般不容易造成DB的當機,只是會造成對DB的週期性壓力。對快取擊穿的解決方案一般可以這樣:Redis中的資料不設定過期時間,然後在快取的物件上新增一個屬性標識過期時間,每次獲取到資料時,校驗物件中的過期時間屬性,如果資料即將過期,則非同步發起一個執行緒主動更新快取中的資料。但是這種方案可能會導致有些請求會拿到過期的值,就得看業務能否可以接受,如果要求資料必須是新資料,則最好的方案則為熱點資料設定為永不過期,然後加一個互斥鎖保證快取的單執行緒寫。
快取穿透
定義:快取穿透是指查詢快取和DB中都不存在的資料。比如通過id查詢商品資訊,id一般大於0,攻擊者會故意傳id為-1去查詢,由於快取是不命中則從DB中獲取資料,這將會導致每次快取都不命中資料導致每個請求都訪問DB,造成快取穿透。
解決方案:快取穿透的解決方案可以分成兩個部分:首先在API層增加基本校驗:使用者鑑權校驗,id校驗。比如使用者鑑權失敗或者id < 0的請求直接進行攔截。其次在快取和DB都取不到資料的時候將將key-value儲存成key-null儲存到Redis, 過期時間可以儲存的短點比如60S,防止短時間內攻擊者不斷髮起請求導致資料庫壓力過大出現當機。
快取雪崩
定義:快取中如果大量快取在一段時間內集中過期了,這時候會發生大量的快取擊穿現象,所有的請求都落在了DB上,由於查詢資料量巨大,引起DB壓力過大甚至導致DB當機。
解決方案:快取雪崩一般沒有完美解決的方法,但是我們可以儘量分析使用者行為,儘量保證key的失效時間比較平均,防止同一時間出現大量快取資料同時過期的現象,並且設定熱點資料永不過期。同時如果為分散式環境下,使用分散式鎖來保證快取的單執行緒寫,這樣可以避免同一時間大量快取失效導致請求全部落在DB上。而我覺得如果可以接受有些請求拿到過期值,最合理的方案實際上就是使用快取擊穿的方案:Redis中的資料不設定過期時間,然後在快取的物件上新增一個屬性標識過期時間,每次獲取到資料時,校驗物件中的過期時間屬性,如果資料即將過期,則非同步發起一個執行緒主動更新快取中的資料。
歡迎關注公眾號:程式設計師周先森。
本文由部落格一文多發平臺 OpenWrite 釋出!