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原稿和原圖片。其它情況一律禁止轉載哦!