描述
本文翻譯3 major problems and solutions in the cache world角钩。文中解釋了緩存穿透(Cache penetration)、緩存雪崩(Cache avalanche)掷匠、熱點數(shù)據(jù)失效,及其解決方法岖圈。
全文
目前的 IO 設(shè)備遠(yuǎn)遠(yuǎn)不能滿足互聯(lián)網(wǎng)應(yīng)用程序的大量讀寫請求讹语。 然后使用內(nèi)存的高速讀寫性能來處理大量的查詢請求的緩存誕生。 然而蜂科,內(nèi)存資源寶貴顽决,在內(nèi)存中存儲全部數(shù)據(jù)顯然是不切實際的。 因此导匣,使用內(nèi)存和 IO 組合才菠,內(nèi)存只存儲熱點數(shù)據(jù),而 IO 設(shè)備存儲全部數(shù)據(jù)贡定。 緩存設(shè)計有很多技巧赋访,不正確的設(shè)計可能會導(dǎo)致嚴(yán)重的后果。 本文將介紹緩存使用中經(jīng)常遇到的三個主要問題缓待,并給出相應(yīng)的解決方案蚓耽。
1 緩存穿透
在大多數(shù)互聯(lián)網(wǎng)應(yīng)用程序中:
- 當(dāng)業(yè)務(wù)系統(tǒng)啟動某個查詢請求時,它首先確定數(shù)據(jù)是否存在于緩存中旋炒;
- 如果有緩存步悠,則直接返回數(shù)據(jù);
- 如果緩存不存在瘫镇,則查詢數(shù)據(jù)庫并返回數(shù)據(jù)
理解了上面的過程之后鼎兽,讓我們討論一下緩存穿透芹壕。
1.1 什么是緩存穿透
業(yè)務(wù)系統(tǒng)要查詢的數(shù)據(jù)不存在!當(dāng)業(yè)務(wù)系統(tǒng)根據(jù)上述流程啟動查詢時接奈,查詢首先轉(zhuǎn)到緩存踢涌,因為緩存不存在,會轉(zhuǎn)到數(shù)據(jù)庫進(jìn)行查詢序宦。 因為數(shù)據(jù)根本不存在睁壁,所以數(shù)據(jù)庫也返回 null。 這是緩存穿透互捌。
總之潘明,業(yè)務(wù)系統(tǒng)訪問根本不存在的數(shù)據(jù)稱為緩存穿透。
1.2 緩存穿透的危險
如果大量查詢請求不存在的數(shù)據(jù)秕噪,那么這些請求將進(jìn)入數(shù)據(jù)庫钳降,數(shù)據(jù)庫壓力將急劇增加,這可能導(dǎo)致系統(tǒng)崩潰腌巾。 (你必須知道遂填,當(dāng)前商業(yè)系統(tǒng)中最脆弱的是 IO,它會在一點點壓力下崩潰澈蝙,所以我們必須想辦法保護(hù)它)吓坚。
1.3 為什么會發(fā)生緩存穿透
導(dǎo)致緩存穿透的原因有很多,一般如下:
- 惡意攻擊故意創(chuàng)建大量不存在的數(shù)據(jù)請求灯荧。 由于緩存中不存在這些數(shù)據(jù)礁击,大量請求會進(jìn)入數(shù)據(jù)庫,這可能導(dǎo)致數(shù)據(jù)庫崩潰逗载。
- 代碼邏輯錯誤哆窿。 這是程序員的鍋,沒什么好說的厉斟,開發(fā)中一定要避免挚躯!
1.4 緩存穿透解決方案
這里有兩種防止緩存穿透的方法。
1.4.1 緩存空數(shù)據(jù)
緩存穿透的原因是緩存中沒有 key 來存儲這些空數(shù)據(jù)捏膨,導(dǎo)致所有這些請求都命中數(shù)據(jù)庫秧均。
然后,我們可以稍微修改業(yè)務(wù)系統(tǒng)的代碼号涯,并將數(shù)據(jù)庫空查詢結(jié)果的 key 存儲在緩存中。 當(dāng)再次發(fā)生該 key 的查詢請求時锯七,緩存直接返回 null链快,而不查詢數(shù)據(jù)庫。
1.4.2 BloomFilter
避免緩存穿透的第二種方法是使用 BloomFilter眉尸。
它需要在緩存之前添加一個屏障域蜗,存儲當(dāng)前數(shù)據(jù)庫中存在的所有 key巨双。
當(dāng)業(yè)務(wù)系統(tǒng)有一個查詢請求時,首先到 BloomFilter 檢查 key 是否存在霉祸。 如果它不存在筑累,則意味著數(shù)據(jù)不存在于數(shù)據(jù)庫中,因此不應(yīng)該查詢緩存而直接返回 null丝蹭。 如果存在慢宗,則繼續(xù)執(zhí)行后續(xù)流程,首先查詢緩存奔穿,如果沒有緩存镜沽,則查詢數(shù)據(jù)庫。
1.4.3 兩個方法比較
這兩種解決方案都可以解決緩存穿透問題贱田,但是使用場景不同缅茉。
對于一些惡意攻擊,查詢的 key 通常是不同的男摧,對于數(shù)據(jù)竊取者更甚蔬墩。 在這一點上,第一個方案 key 太多了耗拓。 因為它需要存儲所有空數(shù)據(jù)的 key筹我,而這些惡意攻擊的 key 通常是不同的,同一個 key 通常只請求一次帆离。 因此蔬蕊,即使緩存了這些空數(shù)據(jù)的 key,由于不再使用第二次哥谷,也無法實現(xiàn)保護(hù)數(shù)據(jù)庫的作用岸夯。 因此,對于空數(shù)據(jù)的 key 不同且重復(fù)請求的概率較低的情況们妥,應(yīng)該選擇第二種方案猜扮。 對于空數(shù)據(jù)的 key 數(shù)量有限且重復(fù)請求的概率很高的情況,應(yīng)該選擇第一個方案监婶。
2 緩存雪崩
2.1 什么是緩存雪崩
上述而知旅赢,緩存實際上起到了保護(hù)數(shù)據(jù)庫的作用。 它幫助數(shù)據(jù)庫承受大量的查詢請求惑惶,從而保護(hù)脆弱的數(shù)據(jù)庫煮盼。
如果由于某種原因緩存中斷,原本被緩存處理的大量查詢請求將像瘋狗一樣涌向數(shù)據(jù)庫带污。 在這一點上僵控,如果數(shù)據(jù)庫不能承受如此巨大的壓力,它就會崩潰鱼冀。
這就是緩存雪崩报破。
2.2 如何避免緩存雪崩
2.2.1 使用緩存集群確保緩存高可用
也就是說悠就,在雪崩發(fā)生之前,要采取預(yù)防措施來防止雪崩的發(fā)生充易。 Ps:分布式高可用性的問題不是今天討論的重點梗脾。 請注意,正常情況下請遵循高可用性相關(guān)的規(guī)范盹靴。
2.2.2 使用 Hystrix
Hystrix 是一個開源的”反雪崩工具” 炸茧,通過熔斷、降級和限流來減少雪崩后的損失鹉究。
Hystrix 是一個使用命令模式的 Java 類庫宇立,每個服務(wù)處理請求都有自己的處理器。 所有請求都經(jīng)過各自的處理器自赔。 處理器記錄當(dāng)前服務(wù)的請求失敗率妈嘹。 一旦發(fā)現(xiàn)當(dāng)前服務(wù)的請求失敗率達(dá)到預(yù)設(shè)值,Hystrix 將拒絕所有后續(xù)的服務(wù)請求绍妨,并返回一個默認(rèn)結(jié)果润脸。 這就是所謂的“熔斷”。 一段時間后他去,Hystrix 將釋放服務(wù)請求的一部分毙驯,并再次計算其請求失敗率。 如果此時請求失敗率滿足預(yù)設(shè)值灾测,則當(dāng)前限制開關(guān)完全打開爆价;如果請求失敗率仍然很高,則拒絕所有服務(wù)請求媳搪。 這就是所謂的“限流”铭段。 Hystrix 將默認(rèn)結(jié)果直接返回給那些被拒絕的請求,稱為“降級” 秦爆。
3 熱點數(shù)據(jù)無效
3.1 什么是熱點數(shù)據(jù)故障
我們通常為緩存設(shè)置一個過期時間序愚。 過期后,數(shù)據(jù)將被緩存直接刪除等限,從而在一定程度上保證了數(shù)據(jù)的實時性爸吮。
但是,對于一些請求量非常大的熱數(shù)據(jù)望门,一旦失效形娇,此時將有大量請求落在數(shù)據(jù)庫上,這可能導(dǎo)致數(shù)據(jù)庫崩潰怒允。 整個過程如下:
如果熱點數(shù)據(jù)失效埂软,那么當(dāng)再次出現(xiàn)對該數(shù)據(jù)的查詢請求時,它將進(jìn)入數(shù)據(jù)庫查詢纫事。 但是勘畔,從請求發(fā)送到數(shù)據(jù)庫到數(shù)據(jù)更新到緩存的時間內(nèi),因為數(shù)據(jù)仍然不在緩存中丽惶,在此期間到達(dá)的查詢請求將落在數(shù)據(jù)庫上炫七,這將給數(shù)據(jù)庫帶來巨大壓力。 此外钾唬,當(dāng)這些請求查詢完成時万哪,緩存將被重復(fù)更新。
3.2 解決方案
3.2.1 互斥鎖
我們可以使用緩存附帶的鎖機(jī)制抡秆。 當(dāng)?shù)谝粋€數(shù)據(jù)庫查詢請求被啟動時奕巍,緩存中的數(shù)據(jù)將被鎖定;此時儒士,到達(dá)緩存的其他查詢請求被阻塞將無法查詢字段的止;在數(shù)據(jù)庫查詢完成并緩存成功后,鎖將被釋放着撩;此時诅福,可以直接從緩存中處理其他阻塞的查詢請求。
當(dāng)一個熱點數(shù)據(jù)失效時拖叙,只有第一個數(shù)據(jù)庫查詢請求被發(fā)送到數(shù)據(jù)庫氓润,所有其他的查詢請求都被阻塞,從而保護(hù)了數(shù)據(jù)庫薯鳍。 但是咖气,由于使用了互斥鎖,其他請求將阻塞等待挖滤,系統(tǒng)的吞吐量將下降崩溪。 這需要考慮實際的業(yè)務(wù),才能實現(xiàn)這一點壶辜。
互斥鎖可以避免熱點數(shù)據(jù)失效導(dǎo)致的數(shù)據(jù)庫損壞問題悯舟。 在實際業(yè)務(wù)中,經(jīng)常會出現(xiàn)一批熱點數(shù)據(jù)同時失效的情況砸民。 那么抵怎,如何防止這種情況下的數(shù)據(jù)庫過載呢?
3.3.2 設(shè)置不同的過期時間
當(dāng)我們在緩存中存儲這些數(shù)據(jù)時岭参,我們可以錯開它們的過期時間反惕。 這可以避免同時發(fā)生失效。 例如演侯,在基礎(chǔ)時間添加/減去一個隨機(jī)數(shù)姿染,以錯開這些緩存的過期時間。