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