Redis快取穿透、擊穿、雪崩,資料庫與快取一致性

雨點的名字發表於2021-12-22
Redis快取穿透、擊穿、雪崩,資料庫與快取一致性

Redis作為高效能非關係型(NoSQL)的鍵值對資料庫,受到了廣大使用者的喜愛和使用,大家在專案中都用到了Redis來做資料快取,但有些問題我們在使用中不得不考慮,其中典型的問題就是:快取穿透快取雪崩快取擊穿與關係型資料庫的一致性

一、快取穿透

1、概念

快取穿透是指查詢一個快取和資料庫不存在的資料。正常的使用快取流程大致是,資料查詢先進行快取查詢,如果key不存在或者key已經過期,再對資料庫進行查詢,並把查詢到的物件,放進快取。如果資料庫查詢物件為空,則不放進快取。

大致流程如下圖所示:

Redis快取穿透、擊穿、雪崩,資料庫與快取一致性

在一些特定場景 例如:秒殺活動。同一時刻會有大量的請求,都在秒殺同一件商品,這些請求同時去查快取中沒有資料,然後又同時訪問資料庫。結果悲劇了,資料庫可能扛不住壓力,直接掛掉。

也會存在有人惡意請求。一般我們的主鍵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)讀請求流程如下:

Redis快取穿透、擊穿、雪崩,資料庫與快取一致性
  • 讀的時候,先讀快取,快取命中的話,直接返回資料;
  • 快取沒有命中的話,就去讀資料庫,從資料庫取出資料,放入快取後,同時返回響應。

2)寫流程如下:

Redis快取穿透、擊穿、雪崩,資料庫與快取一致性

這裡就有兩個問題思考:

1)為什麼寫請求要做刪除庫存操作,而不是做插入快取動作?

2)為什麼是先運算元據庫在刪除舊的快取,能對換一下順序嗎?

2、刪除快取呢,還是更新快取?

我們在操作快取的時候,到底應該刪除快取還是更新快取呢?我們先來看個例子:

Redis快取穿透、擊穿、雪崩,資料庫與快取一致性
  1. 執行緒A先發起一個寫操作,第一步先更新資料庫;
  2. 執行緒B再發起一個寫操作,第二步更新了資料庫;
  3. 由於網路等原因,執行緒B先更新了快取;
  4. 執行緒A更新快取。

這時候,快取儲存的是A的資料(老資料),資料庫儲存的是B的資料(新資料),資料不一致了,髒資料出現啦。如果是刪除快取取代更新快取則不會出現這個髒資料問題。

3、先運算元據庫還是先操作快取

雙寫的情況下,先運算元據庫還是先操作快取?我們再來看一個例子:假設有A、B兩個請求,請求A做更新操作,請求B做查詢讀取操作。

Redis快取穿透、擊穿、雪崩,資料庫與快取一致性
  1. 執行緒A發起一個寫操作,第一步del cache;
  2. 此時執行緒B發起一個讀操作,cache miss;
  3. 執行緒B繼續讀DB,讀出來一個老資料;
  4. 然後執行緒B把老資料設定入cache;
  5. 執行緒A寫入DB最新的資料;

這樣就有問題啦,快取和資料庫的資料不一致了。快取儲存的是老資料,資料庫儲存的是新資料。因此,Cache-Aside快取模式,選擇了先運算元據庫而不是先操作快取。


宣告

公眾號如需轉載該篇文章,那麻煩在文章的頭部 宣告是轉至公眾號: 後端元宇宙。尊重作者辛苦勞動果實嘛。同時也可以問本人要該文章markdown原稿和原圖片。其它情況一律禁止轉載哦!

相關文章