一 Redis雪崩、穿透和擊穿
1. Redis雪崩:
?Redis雪崩是指在某一時刻,緩存中大量的緩存數據同時失效或過期,導致大量的請求直接打到后端數據庫,導致數據庫負載劇增,引發性能問題甚至崩潰。這通常是因為緩存數據的過期時間設置過于集中,或者在同一時間段內大量緩存同時失效造成的。
2. Redis穿透
?Redis穿透是指惡意或者異常請求查詢一個不存在于緩存和數據庫中的數據,導致每次請求都會直接訪問數據庫,增加了數據庫負擔。這可能是攻擊者故意進行的,也可能是由于業務邏輯問題造成的。
3. Redis擊穿:
?Redis擊穿是指某個熱點數據突然失效或被刪除,而此時大量請求正好同時訪問該熱點數據,導致這些請求都直接打到數據庫上,導致數據庫壓力激增。與雪崩不同,擊穿是因為某個特定的緩存數據失效導致。
示例:
讓我們以一個簡單的Java代碼示例來說明Redis雪崩、穿透和擊穿的概念。
假設有一個電影信息查詢系統,用戶可以根據電影ID查詢電影信息。我們使用Redis作為緩存來存儲電影信息,但是只對熱門電影設置了緩存,其他電影沒有被緩存。
@Service
public class MovieService {@Autowiredprivate MovieRepository movieRepository;@Autowiredprivate Jedis jedis;public Movie getMovieInfo(String movieId) {String cacheKey = "movie:" + movieId;String cachedInfo = jedis.get(cacheKey);if (cachedInfo == null) {Movie movie = movieRepository.findById(movieId);if (movie != null) {jedis.setex(cacheKey, 3600, movie.toString()); // 緩存1小時return movie;}}return Movie.fromString(cachedInfo);}
}
Redis雪崩示例:
假設在某一時刻,緩存中存儲了很多電影信息,這些緩存在同一時間內同時失效,導致大量請求直接訪問數據庫,造成數據庫壓力激增。
Redis穿透示例:
有一個惡意用戶不斷發送不存在的電影ID,每次請求都會繞過緩存,直接查詢數據庫,導致數據庫壓力增加。
Redis擊穿示例:
假設某個熱門電影的緩存在某個時間點失效,而在這個時間點正好有大量用戶同時查詢該電影信息,導致所有請求直接訪問數據庫,造成數據庫壓力激增。
二 解決方案
2.1 對緩存數據的過期時間進行隨機化,避免集中失效。
-
選擇隨機時間范圍: 首先,你需要選擇一個適當的隨機時間范圍,用于分散緩存數據的過期時間。例如,你可以選擇在原始過期時間基礎上添加一個隨機的秒數,這樣每個緩存項的過期時間就會稍微有所不同。
-
生成隨機時間: 在獲取緩存數據時,生成一個隨機的秒數,然后將其添加到原始過期時間上,得到一個新的過期時間。
-
設置緩存數據: 將緩存數據存儲到Redis中,并設置使用上一步生成的新過期時間。
?@Service public class CacheService {@Autowiredprivate Jedis jedis;public String getCachedData(String key) {String cachedData = jedis.get(key);if (cachedData == null) {// 查詢數據庫獲取數據String dbData = Database.queryData(key);if (dbData != null) {// 生成隨機的過期時間(在1小時基礎上隨機增加0-300秒)int originalExpireTime = 3600; // 1小時的秒數int randomSeconds = new Random().nextInt(300); // 0到300秒的隨機數int cacheDuration = originalExpireTime + randomSeconds;// 將數據存儲到緩存并設置隨機過期時間jedis.setex(key, cacheDuration, dbData);return dbData;}}return cachedData;} }
2.2 使用布隆過濾器來過濾惡意請求,防止緩存穿透。
使用布隆過濾器來過濾惡意請求,以防止緩存穿透是一種常見的防御策略。布隆過濾器是一種數據結構,用于判斷一個元素是否存在于集合中,它可以高效地進行快速查詢,但可能會有一定的誤判率。
下面是一個使用Spring Boot和布隆過濾器來防止緩存穿透的詳細舉例:
步驟:引入依賴: 在Spring Boot項目中,添加所需的依賴,包括Spring Boot、Jedis和Google Guava(用于實現布隆過濾器)。初始化布隆過濾器: 在啟動時初始化一個布隆過濾器,用于存儲已查詢的緩存鍵。查詢緩存數據: 在獲取數據之前,首先檢查布隆過濾器,如果緩存鍵可能存在,則再查詢緩存。import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;@Service
public class CacheService {private final Jedis jedis;private final BloomFilter<String> bloomFilter;@Autowiredpublic CacheService(Jedis jedis) {this.jedis = jedis;this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 1000, 0.01); // 初始化布隆過濾器}public String getCachedData(String key) {if (!bloomFilter.mightContain(key)) { // 判斷是否可能存在于集合中return null; // 不再查詢緩存和數據庫,直接返回null}String cachedData = jedis.get(key);if (cachedData == null) {// 查詢數據庫獲取數據String dbData = Database.queryData(key);if (dbData != null) {jedis.setex(key, 3600, dbData); // 緩存1小時bloomFilter.put(key); // 將鍵添加到布隆過濾器中return dbData;}}return cachedData;}
}
2.3 使用互斥鎖(例如分布式鎖)來防止擊穿,只允許一個請求去查詢數據庫,其他請求等待或直接使用緩存。
使用互斥鎖(分布式鎖)來防止擊穿是一種常見的策略,可以確保在緩存失效的情況下,只有一個請求能夠去查詢數據庫,其他請求需要等待該請求完成或直接使用緩存。下面是一個使用Spring Boot和Jedis實現分布式鎖來防止擊穿的代碼示例:
步驟:1、引入依賴: 在Spring Boot項目中,添加所需的依賴,包括Spring Boot和Jedis。2、獲取分布式鎖: 在查詢數據庫之前,使用分布式鎖來確保只有一個請求能夠進行數據庫查詢。3、釋放分布式鎖: 在查詢完成后,釋放分布式鎖,讓其他請求能夠繼續執行@Service
public class CacheService {@Autowiredprivate Jedis jedis;public String getCachedData(String key) {String cachedData = jedis.get(key);if (cachedData == null) {// 嘗試獲取分布式鎖,設置鎖的過期時間,防止死鎖String lockKey = "lock:" + key;String lockValue = "lockValue";SetParams params = new SetParams().ex(60).nx(); // 設置60秒過期時間,只有不存在時才設置String acquiredLock = jedis.set(lockKey, lockValue, params);if (acquiredLock != null) {try {// 查詢數據庫獲取數據String dbData = Database.queryData(key);if (dbData != null) {jedis.setex(key, 3600, dbData); // 緩存1小時return dbData;}} finally {// 釋放分布式鎖jedis.del(lockKey);}} else {// 等待一段時間后重新查詢緩存try {Thread.sleep(200); // 可以根據實際情況調整等待時間} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 重新查詢緩存cachedData = jedis.get(key);}}return cachedData;}
}
2.4 合理設置緩存策略,確保熱門數據始終保持緩存,避免緩存雪崩。
確保熱門數據始終保持緩存,避免緩存雪崩,需要采取一些合理的緩存策略。以下是一些常見的合理方案:
1. 定時刷新緩存: 使用定時任務或調度器,定期刷新熱門數據的緩存。這可以確保緩存中的數據始終保持最新,避免數據過期。
2. 永不過期策略:對于熱門數據,可以設置永不過期的緩存策略。但要注意,如果熱門數據發生變化,需要手動更新緩存。
3. 熱點數據預加載:在應用啟動時,預先加載熱門數據到緩存中,確保緩存中存在最常用的數據。
4. 基于訪問頻率的過期策略:?根據數據的訪問頻率動態調整過期時間。訪問頻率高的數據設置較長的過期時間,訪問頻率低的數據設置較短的過期時間。
5. 分布式鎖控制:** 在緩存失效時,使用分布式鎖來防止多個請求同時查詢數據庫,確保只有一個請求進行查詢并更新緩存。
6. 降級策略:?如果緩存失效,可以暫時使用降級策略,例如返回默認值或靜態數據,以避免直接訪問數據庫。
7. 多級緩存:?使用多級緩存架構,將熱門數據存儲在多個緩存層中,例如內存緩存和分布式緩存,以提高數據的訪問速度和穩定性。
8. 請求合并:?對于同時涌入的大量請求,可以考慮將它們合并成一個請求,只查詢一次數據庫,然后將結果分發給多個請求。
9. 緩存預熱:?在系統負載較低的時候,提前將熱門數據加載到緩存中,以減少在高負載時的數據庫壓力。
10. 動態緩存策略:?根據系統的實際情況,動態調整緩存策略,例如根據時間段、節假日等因素來設置不同的緩存策略。
選擇合適的方案取決于你的業務需求和系統特點。通常,結合多個方案可以更好地保護熱門數據,避免緩存雪崩問題。