緩存擊穿、穿透、雪崩專項測試
京東科技作者:劉須華
一、背景概述:
R2M緩存的使用,極大的提升了應用程序的性能和效率,特別是數據查詢方面。而緩存最常見的問題是緩存穿透、擊穿和雪崩,在高并發下這三種情況都會有大量請求落到數據庫,導致數據庫資源占滿,引起數據庫故障。平時對緩存測試時除了關注增刪修改查詢等基本功能,應該要重點關注緩存穿透、擊穿和雪崩三種異常場景的測試覆蓋,避免出現線上事故。
二、基本概念說明:
1、緩存擊穿:是指在超級熱點數據突然過期,導致針對超級熱點的數據請求在過期期間直接打到數據庫,這樣數據庫服務器會因為某一超熱數據導致壓力過大而崩掉。
2、緩存穿透:是指查找的數據在緩存和數據庫中都不存在,導致每一次請求數據從緩存中都獲取不到,而將請求打到數據庫服務器,但數據庫中也沒有對應的數據,最后每一次請求都到數據庫;如果在高并發場景或有人惡意攻擊,就會導致后臺數據庫服務器壓力增大,最終系統可能崩掉。
3、緩存雪崩:是指突然緩存層不可用,導致大量請求直接打到數據庫,最終由于數據庫壓力過大可能導致系統崩掉。緩存層不可用指以下兩方面:緩存服務器宕機,系統將請求打到數據庫; 緩存數據突然大范圍集中過期失效,導致大量請求打到數據庫重新加載數據, 與緩存擊穿的區別在于這里針對很多key緩存,前者則是某一個key。
三、測試工具(非必須):
1、使用Titan壓測平臺進行并發請求測試
2、使用jmeter工具模擬并發請求
四、測試方法舉例說明(非必須):
環境:測試環境
工具:jmeter
(1)緩存穿透場景
測試方法:查詢一個根本不存在的數據,緩存層和存儲層都不會命中。
查詢接口相關代碼實現:
通過JMETER模擬多次重復調用:單線程重復調用
查看日志結果: 從日志可以看出:執行并發請求后, 所有請求每次都走向了數據庫。
預防方案:
當數據庫查詢為空時,將緩存賦值默認值,后續查詢都走緩存,減少數據庫壓力。
上述接口,增加賦值為empty,則第一次查詢到數據庫為空,后續查詢都查詢到緩存中,緩存值為empty。
再次執行并發測試:從日志可以看出,可以看出每個ID都只執行了一次數據庫查詢并設置緩存,之后請求都命中了緩存,有效防止了緩存穿透問題。
(2)緩存擊穿場景
測試方法:對某個Key有大量的并發請求,這時從緩存中刪除這個key。模擬熱key過期失效的場景。這個時候大并發的請求可能會瞬間把后端DB壓垮。
接口相關部分代碼實現:
操作步驟:
1、查詢pin為liuxuhua的請求,這時pin為liuxuhua的數據會加載到緩存
2、再次查詢pin為liuxuhua的請求,命中緩存
3、50并發請求pin為liuxuhua的數據,這個時候請求全部命中緩存
4、將pin為liuxuhua的緩存手動刪除,模擬緩存失效
5、50并發請求pin為liuxuhua的數據,這個時候大量請求走向數據庫,pin為liuxuhua的緩存被擊穿
查看日志結果:
預防方案:
在設置默認緩存值的基礎上,進行加鎖處理。只有拿到鎖的第一個線程去請求數據庫,然后插入緩存,當然每次拿到鎖的時候都要去查詢一下緩存有沒有。
從日志記錄可以看到只有一個請求執行了數據庫查詢并設置緩存,其他請求都命中了緩存, 有效防止了緩存的擊穿。
(3)緩存雪崩
測試方法:對多個使用到緩存的接口進行并發調用,設置這些緩存時間已過期(即刪除緩存),調用時這些接口查詢緩存時無數據,去查詢數據庫,這些請求都指向數據庫,數據庫壓力增大,耗時增加。
模擬接口:
通過JMETER模擬多次重復調用:單線程多接口重復調用
查看日志結果:可以看出大量請求到達數據庫,并且同一個pin或id執行了多次數據庫查詢
預防方案:
增加限流操作,即接口頻繁調用時,增加一個緩存,設置時間為3s,3s內處理一定次數的請求,超過限制次數的請求直接返回結果,不做處理。
接口:3s內處理6次請求,超過則不處理;
從日志可以看出:可以看到每個都只查詢了一次數據庫并設置緩存,之后的請求都命中了緩存
五、測試指標:(或者叫通過標準,包括關注點以及意義)
1、模擬緩存穿透場景測試,每個不存在的數據都只執行了一次數據庫查詢并設置緩存,之后請求都命中了緩存,有效防止了緩存穿透問題。
2、模擬緩存雪崩場景測試,每個緩存失效的數據都只執行了一次數據庫查詢并設置緩存,之后請求都命中了緩存。
3、模擬緩存擊穿場景測試,緩存失效的那個數據只有一個請求執行了數據庫查詢并設置緩存,其他請求都命中了緩存。
六、適用業務場景:
1、秒殺活動
2、熱門營銷活動
3、618和雙11大促
七、研發側常見解決方案(參考):
1、緩存穿透解決方案:
(1)緩存空值 之所以發生穿透,是因為緩存中沒有存儲這些數據的key,從而每次都查詢數據庫 我們可以為這些key在緩存中設置對應的值為null,后面查詢這個key的時候就不用查詢數據庫了 當然為了健壯性,我們要對這些key設置過期時間,以防止真的有數據
(2)BloomFilter BloomFilter 類似于一個hbase set 用來判斷某個元素(key)是否存在于某個集合中 我們把有數據的key都放到BloomFilter中,每次查詢的時候都先去BloomFilter判斷,如果沒有就直接返回null 注意BloomFilter沒有刪除操作,對于刪除的key,查詢就會經過BloomFilter然后查詢緩存再查詢數據庫,所以BloomFilter可以結合緩存空值用,對于刪除的key,可以在緩存中緩存null 緩存擊穿
2、緩存擊穿解決方案:
采用分布式鎖,只有拿到鎖的第一個線程去請求數據庫,然后插入緩存,當然每次拿到鎖的時候都要去查詢一下緩存有沒有
3、緩存雪崩解決方案:
(1)采用集群,降低服務宕機的概率
(2)ehcache本地緩存 + 限流&降級
(3)均勻過期,通常可以為有效期增加隨機值