緩存穿透
緩存的目的是為了緩解 CPU 或者 I/O 的壓力,譬如對數據庫做緩存,大部分流量都從緩存中直接返回,只有緩存未能命中的數據請求才會流到數據庫中,這樣數據庫壓力自然就減小了。
如果查詢的數據在數據庫中根本不存在的話,緩存里自然也不會有,
這類請求的流量每次都不會命中,這種查詢不存在數據的現象被稱為緩存穿透。
緩存穿透有可能是業務邏輯本身就存在的固有問題,也有可能是被惡意攻擊的所導致,為了解決緩存穿透,通常會采取下面兩種辦法:
-
對於業務邏輯本身就不能避免的緩存穿透,可以約定在一定時間內對返回為空的 Key 值依然進行緩存(注意是正常返回但是結果為空,不應把拋異常的也當作空值來緩存了),使得在一段時間內緩存最多被穿透一次。
如果后續業務在數據庫中對該 Key 值插入了新記錄,那應當在插入之后主動清理掉緩存的 Key 值。如果業務時效性允許的話,也可以將對緩存設置一個較短的超時時間來自動處理。
-
對於惡意攻擊導致的緩存穿透,通常會在緩存之前設置一個布隆過濾器來解決。所謂惡意攻擊是指請求者刻意構造數據庫中肯定不存在的 Key 值,然后發送大量請求進行查詢。布隆過濾器是用最小的代價來判斷某個元素是否存在於某個集合的辦法。如果布隆過濾器給出的判定結果是請求的數據不存在,那就直接返回即可,連緩存都不必去查。雖然維護布隆過濾器本身需要一定的成本,但比起攻擊造成的資源損耗仍然是值得的。
緩存擊穿
我們都知道緩存的基本工作原理是首次從真實數據源加載數據,完成加載后回填入緩存,以后其他相同的請求就從緩存中獲取數據,緩解數據源的壓力。
如果緩存中某些熱點數據忽然因某種原因失效了,譬如典型地由於超期而失效,此時又有多個針對該數據的請求同時發送過來,這些請求將全部未能命中緩存,都到達真實數據源中去,導致其壓力劇增,這種現象被稱為緩存擊穿。
要避免緩存擊穿問題,通常會采取下面的兩種辦法:
-
加鎖同步,以請求該數據的 Key 值為鎖,使得只有第一個請求可以流入到真實的數據源中,其他線程采取阻塞或重試策略。如果是進程內緩存出現問題,施加普通互斥鎖即可,如果是分布式緩存中出現的問題,就施加分布式鎖,這樣數據源就不會同時收到大量針對同一個數據的請求了。
-
熱點數據由代碼來手動管理,緩存擊穿是僅針對熱點數據被自動失效才引發的問題,對於這類數據,可以直接由開發者通過代碼來有計划地完成更新、失效,避免由緩存的策略自動管理。**
緩存雪崩
緩存擊穿是針對單個熱點數據失效,由大量請求擊穿緩存而給真實數據源帶來壓力。有另一種可能是更普遍的情況,不需要是針對單個熱點數據的大量請求,而是由於大批不同的數據在短時間內一起失效,導致了這些數據的請求都擊穿了緩存到達數據源,同樣令數據源在短時間內壓力劇增。
出現這種情況,往往是系統有專門的緩存預熱功能,也可能大量公共數據是由某一次冷操作加載的,這樣都可能出現由此載入緩存的大批數據具有相同的過期時間,在同一時刻一起失效。還有一種情況是緩存服務由於某些原因崩潰后重啟,此時也會造成大量數據同時失效,這種現象被稱為緩存雪崩。要避免緩存雪崩問題,通常會采取下面的三種辦法:
-
提升緩存系統可用性,建設分布式緩存的集群。
-
啟用透明多級緩存,各個服務節點一級緩存中的數據通常會具有不一樣的加載時間,也就分散了它們的過期時間。
-
將緩存的生存期從固定時間改為一個時間段內的隨機時間,譬如原本是一個小時過期,那可以緩存不同數據。
總結
穿透:緩存不存在,數據庫不存在,高並發,少量key。
擊穿:緩存不存在,數據庫存在,高並發,少量key。
雪崩:緩存不存在,數據庫存在,高並發,大量key。
Reference
《鳳凰架構》