Redis緩存穿透、擊穿、雪崩,數據庫與緩存一致性
- 2021 年 12 月 22 日
- 筆記
- 【數據庫】-- Redis
Redis作為高性能非關係型(NoSQL)的鍵值對數據庫,受到了廣大用戶的喜愛和使用,大家在項目中都用到了Redis來做數據緩存,但有些問題我們在使用中不得不考慮,其中典型的問題就是:緩存穿透、緩存雪崩、緩存擊穿和與關係型數據庫的一致性。
一、緩存穿透
1、概念
緩存穿透是指查詢一個緩存和數據庫不存在的數據
。正常的使用緩存流程大致是,數據查詢先進行緩存查詢,如果key不存在或者key已經過期,再對數據庫進行查詢,並把查詢到的對象,放進緩存。如果數據庫查詢對象為空,則不放進緩存。
大致流程如下圖所示:
在一些特定場景 例如:秒殺活動
。同一時刻會有大量的請求,都在秒殺同一件商品,這些請求同時去查緩存中沒有數據,然後又同時訪問數據庫。結果悲劇了,數據庫可能扛不住壓力,直接掛掉。
也會存在有人惡意請求。一般我們的主鍵ID都是無符號的自增類型,有些人想要搞垮你的數據庫,每次請求都用負數ID,而ID為負數的記錄在數據庫根本就沒有。就會每次都去查詢數據庫,而每次查詢都是空,每次又都不會進行緩存。假如有惡意攻擊,就可以利用這個漏洞,對數據庫造成壓力,甚至壓垮數據庫。
3、解決方案
1) 驗證攔截
接口層進行校驗,如鑒定用戶權限,對ID之類的字段做基礎的校驗,如id<=0的字段直接攔截。
2) 布隆過濾器
我們可以提前將真實正確的商品id,添加到過濾器當中,每次再進行查詢時,先確認要查詢的id是否在過濾器當中,如果不在,則說明id為非法id,則不需要進行後續的查詢步驟了。
布隆過濾器是一種比較獨特數據結構,有一定的誤差。布隆過濾器的特點就是 如果它說不存在那肯定不存在,如果它說存在,那數據有可能實際不存在
。
它最大的優點就是性能高,空間佔用率及小。
3) 緩存空對象
當存儲層不命中後,即使返回的空對象也將其緩存起來,同時會設置一個過期時間,之後再訪問這個數據將會從緩存中獲取,保護了後端數據源。
但是這種方法會存在兩個問題:
- 如果空值能夠被緩存起來,這就意味着
緩存需要更多的空間存儲更多的鍵
,因為這當中可能會有很多的空值的鍵; - 即使對空值設置了過期時間,還是會存在
緩存層和存儲層的數據會有一段時間窗口的不一致
,這對於需要保持一致性的業務會有影響。
二、緩存擊穿
1、概念
緩存擊穿,是指緩存中沒有但數據庫中有的數據
,並且某一個key非常熱點
,在不停的扛着大並發,大並發集中對這一個點進行訪問,當這個key緩存時間到期,持續的大並發就穿破緩存,直接請求數據庫,導致壓垮數據庫。
2、解決方案
1)設置熱點數據永遠不過期。
這個方法就比較粗暴,,如果你的熱點數據要求實時性比較低,那麼可以設置熱點數據在熱點時段不過期
,在訪問低峰期過期,比如每天凌晨過期。
2) 使用分佈式鎖
互斥鎖可以控制查詢數據庫的線程訪問,但這種方案會導致系統的吞吐量下降,需要根據實際情況使用。
public static String getData(String key) throws InterruptedException {
//從Redis查詢數據
String result = getDataByKV(key);
//參數校驗
if (StringUtils.isBlank(result)) {
try {
//獲得鎖
if (reenLock.tryLock()) {
//去數據庫查詢
result = getDataByDB(key);
//校驗
if (StringUtils.isNotBlank(result)) {
//插進緩存
setDataToKV(key, result);
}
} else {
//睡一會再拿
Thread.sleep(100L);
result = getData(key);
}
} finally {
//釋放鎖
reenLock.unlock();
}
}
return result;
}
三、緩存雪崩
1、概念
緩存雪崩表示在某一時間段緩存集中失效
,導致請求全部走數據庫,引起數據庫壓力過大甚至down機。和緩存擊穿不同的是,緩存擊穿指並發查同一條數據
,緩存雪崩是不同數據
都過期了,很多數據都查不到從而查數據庫。
使緩存集中失效的原因:
1)、redis服務器掛掉了。
Redis 集群產生了大面積故障;緩存失敗,此時仍有大量請求去訪問 Redis緩存服務器;在大量 Redis 請求失敗後,這些請求將會去訪問數據庫;
2、對緩存數據設置了相同的過期時間,導致某時間段內緩存集中失效。
2、解決方案
1)實現Redis的高可用
【事前】搭建Redis 哨兵(Sentinel) 或 Redis 集群(Cluster) 都可以做到高可用;
【事中】緩存降級(臨時支持):當訪問次數急劇增加導致服務出現問題時,我們如何確保服務仍然可用。在國內使用比較多的是 Hystrix,它通過熔斷、降級、限流三個手段來降低雪崩發生後的損失。只要確保數據庫不死,系統總可以響應請求。
【事後】Redis備份和快速預熱:Redis數據備份和恢復、快速緩存預熱。
2)緩存數據的過期時間設置隨機,防止同一時間大量數據過期現象發生。
(1)採取不同分類商品,緩存不同周期
。在同一分類中的商品,加上一個隨機因子
。這樣能儘可能分散緩存過期時間,而且,熱門類目的商品緩存時間長一些,冷門類目的商品緩存時間短一些,也能節省緩存服務的資源。
(2)如果緩存數據庫是分佈式部署,將 熱點數據均勻分佈在不同的緩存數據庫中。
(3)設置熱點數據永遠不過期。
四、數據庫與緩存一致性
使用緩存,可以降低耗時,提供系統吞吐性能。但是,使用緩存,會存在數據一致性的問題。
1、旁路緩存模式
一般我們使用緩存,都是旁路緩存模式,它的特點就是讀的時候插入緩存,寫的時候刪除緩存
。
1)讀請求流程如下:
- 讀的時候,先讀緩存,緩存命中的話,直接返回數據;
- 緩存沒有命中的話,就去讀數據庫,從數據庫取出數據,放入緩存後,同時返迴響應。
2)寫流程如下:
這裡就有兩個問題思考:
1)為什麼寫請求要做刪除庫存操作,而不是做插入緩存動作?
2)為什麼是先操作數據庫在刪除舊的緩存,能對換一下順序嗎?
2、刪除緩存呢,還是更新緩存?
我們在操作緩存的時候,到底應該刪除緩存還是更新緩存呢?我們先來看個例子:
- 線程A先發起一個寫操作,第一步先更新數據庫;
- 線程B再發起一個寫操作,第二步更新了數據庫;
- 由於網絡等原因,線程B先更新了緩存;
- 線程A更新緩存。
這時候,緩存保存的是A的數據(老數據),數據庫保存的是B的數據(新數據),數據不一致了,臟數據出現啦
。如果是刪除緩存取代更新緩存則不會出現這個臟數據問題。
3、先操作數據庫還是先操作緩存
雙寫的情況下,先操作數據庫還是先操作緩存?我們再來看一個例子:假設有A、B兩個請求,請求A做更新操作,請求B做查詢讀取操作。
- 線程A發起一個寫操作,第一步del cache;
- 此時線程B發起一個讀操作,cache miss;
- 線程B繼續讀DB,讀出來一個老數據;
- 然後線程B把老數據設置入cache;
- 線程A寫入DB最新的數據;
這樣就有問題啦,緩存和數據庫的數據不一致了。緩存保存的是老數據,數據庫保存的是新數據。因此,Cache-Aside緩存模式,選擇了先操作數據庫而不是先操作緩存。
聲明
公眾號如需轉載該篇文章,那麻煩在文章的頭部 聲明是轉至公眾號: 後端元宇宙。尊重作者辛苦勞動果實嘛。同時也可以問本人要該文章markdown原稿和原圖片。其它情況一律禁止轉載哦!