最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis緩存擊穿的用法及說明

 更新時間:2026年04月26日 10:24:25   作者:風(fēng)吹迎面入袖涼  
文章介紹了緩存擊穿的概念、核心原因及其對系統(tǒng)的危害,并提出了兩種解決方案:互斥鎖方案和邏輯過期方案,互斥鎖方案能保證數(shù)據(jù)一致性但性能較低,邏輯過期方案則能提供高可用性和性能

一. 什么是緩存擊穿

緩存擊穿,指的是某一個熱點key(被高頻訪問的key),在緩存中過期失效的瞬間,大量的請求直接穿透緩存,涌向數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫瞬時壓力驟增,甚至被壓垮的現(xiàn)象

Tip:當(dāng)請求打過來時,未在緩存中命中,去數(shù)據(jù)庫查詢數(shù)據(jù)就可能涉及到了很多表和數(shù)據(jù)庫的查詢匯總。這種情況就會出現(xiàn)查詢數(shù)據(jù)時間很長,在這期間大量請求涌入數(shù)據(jù)庫,它可能瞬間扛不住,就會出現(xiàn)響應(yīng)超時、報錯,甚至宕機

舉個例子:比如電商平臺的“爆款商品詳情頁”,這個商品的key在Redis中緩存了1小時,平時所有請求都走緩存,數(shù)據(jù)庫幾乎無壓力。但當(dāng)這個key過期的那一刻,剛好有1000個用戶同時訪問這個商品,此時緩存中沒有數(shù)據(jù),所有1000個請求都會直接打向數(shù)據(jù)庫,數(shù)據(jù)庫瞬間扛不住,就會出現(xiàn)響應(yīng)超時、報錯,甚至宕機。

這里要注意區(qū)分兩個易混淆概念:

  • 緩存穿透:是請求的key本身就不存在于緩存和數(shù)據(jù)庫中,請求一直穿透到數(shù)據(jù)庫。

  • 緩存擊穿:是請求的key存在于數(shù)據(jù)庫中,但緩存剛好過期,瞬時請求穿透到數(shù)據(jù)庫。

二. 緩存擊穿的核心原因

1. 存在高頻訪問的熱點key

如果key不是熱點,即使緩存過期,也只有少量請求穿透到數(shù)據(jù)庫,不會造成太大影響。只有當(dāng)key被高頻訪問,才會在緩存失效瞬間對數(shù)據(jù)庫造成巨大壓力。

2. 緩存key過期失效

Redis的key都有過期時間,目的是為了釋放內(nèi)存,避免無效數(shù)據(jù)占用空間。但如果熱點key的過期時間設(shè)置不合理,或者剛好在請求高峰時過期,就會觸發(fā)擊穿。

比如:把爆款商品的緩存時間設(shè)置為1小時,而剛好在晚上8點(用戶訪問高峰)過期,就極易引發(fā)擊穿。

3. 緩存與數(shù)據(jù)庫之間無兜底機制

如果緩存失效后,沒有任何限流、降級、重試的機制,所有請求會毫無阻攔地沖向數(shù)據(jù)庫,而數(shù)據(jù)庫的并發(fā)處理能力遠低于Redis,很容易被壓垮。

三. 緩存擊穿的危害

數(shù)據(jù)庫壓力驟增:瞬時大量請求穿透到數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫CPU、內(nèi)存、IO占用率飆升,響應(yīng)時間大幅延長。

系統(tǒng)響應(yīng)超時:數(shù)據(jù)庫處理不過來請求,會導(dǎo)致接口響應(yīng)超時,前端出現(xiàn)加載失敗、白屏等問題。

數(shù)據(jù)庫宕機:如果請求量過大,超過數(shù)據(jù)庫的承載極限,會導(dǎo)致數(shù)據(jù)庫宕機,進而引發(fā)整個系統(tǒng)服務(wù)不可用。

連鎖反應(yīng):數(shù)據(jù)庫宕機后,即使緩存恢復(fù),后續(xù)請求依然無法正常處理,可能導(dǎo)致服務(wù)雪崩,依賴該數(shù)據(jù)庫的其他服務(wù)也跟著報錯。

四. 緩存擊穿的解決方案

方案一:互斥鎖

當(dāng)大量請求過來后走緩存

  • 命中:直接返回緩存數(shù)據(jù)
  • 未命中:讓一個請求獲取到鎖去查詢數(shù)據(jù)庫重建緩存數(shù)據(jù),其余未命中且未獲取到鎖的請求則休眠一段時間后,再次查詢緩存。
  • 特點:保證了很強的一致性,但是性能很差(有一段時間內(nèi)其余未獲取鎖請求都在空閑等待)

// 1. 注入RedisTemplate(SpringBoot環(huán)境)
@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 2. 互斥鎖核心方法(獲取鎖+查詢數(shù)據(jù)庫+更新緩存)
public Object getValueByMutexLock(String key) {
    // 第一步:查詢緩存
    Object value = redisTemplate.opsForValue().get(key);
    if (value != null) {
        return value; // 緩存存在,直接返回
    }
    
    // 第二步:緩存不存在,嘗試獲取分布式鎖
    String lockKey = "lock:" + key; // 鎖key,與業(yè)務(wù)key綁定,避免鎖沖突
    String lockValue = UUID.randomUUID().toString(); // 唯一值,用于釋放鎖
    boolean isLock = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS); // 鎖過期時間3秒(根據(jù)數(shù)據(jù)庫查詢耗時調(diào)整)
    
    if (isLock) {
        try {
            // 第三步:獲取鎖成功,查詢數(shù)據(jù)庫
            value = queryDatabase(key); // 自定義方法,查詢數(shù)據(jù)庫數(shù)據(jù)
            // 第四步:將數(shù)據(jù)庫數(shù)據(jù)寫入緩存,設(shè)置過期時間(避免再次擊穿)
            redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
            return value;
        } finally {
            // 第五步:釋放鎖(必須在finally中,避免死鎖)
            // 對比value確保是自己的鎖,避免誤釋放他人的鎖
            if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
    } else {
        // 第六步:獲取鎖失敗,重試(間隔100ms,避免頻繁重試)
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return getValueByMutexLock(key); // 遞歸重試,也可使用循環(huán)
    }
}

// 模擬數(shù)據(jù)庫查詢方法
private Object queryDatabase(String key) {
    // 實際業(yè)務(wù)中替換為真實數(shù)據(jù)庫查詢邏輯(如MyBatis查詢)
    return "數(shù)據(jù)庫查詢到的" + key + "對應(yīng)數(shù)據(jù)";
}

方案二:邏輯過期

我們不再設(shè)置key的物理過期時間,而是在緩存數(shù)據(jù)中嵌入一個邏輯過期時間字段。

當(dāng)大量請求過來后走緩存判斷查詢字段是否已經(jīng)過期

  • 未過期:直接返回緩存數(shù)據(jù)
  • 已過期:獲取互斥鎖, 此時開啟一個新的線程去查詢數(shù)據(jù)庫重建緩存,并且將其邏輯時間設(shè)置為   當(dāng)前時間+xxx分鐘,釋放鎖的任務(wù)交給該新線程,開啟新的線程后,直接返回過期的數(shù)據(jù)。在重建緩存過程中,其他線程進來查詢緩存判斷邏輯時間已經(jīng)過期,且獲取鎖失敗,直接給它返回剛剛緩存中查詢出來的過期數(shù)據(jù)即可。
  • 特點:不能保證數(shù)據(jù)絕對一致,但是高可用、性能優(yōu)秀

// 1. 定義緩存數(shù)據(jù)封裝類(封裝業(yè)務(wù)數(shù)據(jù)+邏輯過期時間)
@Data
public class CacheData<T> {
    // 業(yè)務(wù)數(shù)據(jù)
    private T data;
    // 邏輯過期時間(時間戳,單位:毫秒)
    private Long expireTime;
}

// 2. 注入依賴(SpringBoot環(huán)境)
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 異步線程池(用于邏輯過期后異步更新緩存)
@Autowired
private ThreadPoolTaskExecutor asyncTaskExecutor;

// 3. 邏輯過期核心方法
public Object getValueByLogicalExpire(String key) {
    // 第一步:查詢Redis緩存(獲取封裝后的CacheData對象)
    CacheData<Object> cacheData = (CacheData<Object>) redisTemplate.opsForValue().get(key);
    if (cacheData == null) {
        // 緩存不存在(首次請求/緩存被手動刪除),此處可返回兜底數(shù)據(jù)或查詢數(shù)據(jù)庫
        return queryDatabase(key);
    }
    
    // 第二步:判斷邏輯過期時間是否已到
    Long currentTime = System.currentTimeMillis();
    if (currentTime < cacheData.getExpireTime()) {
        // 邏輯未過期,直接返回業(yè)務(wù)數(shù)據(jù)
        return cacheData.getData();
    }
    
    // 第三步:邏輯已過期,返回舊數(shù)據(jù),同時異步更新緩存
    asyncTaskExecutor.execute(() -> {
        try {
            // 異步查詢數(shù)據(jù)庫最新數(shù)據(jù)
            Object newData = queryDatabase(key);
            // 重新封裝CacheData,設(shè)置新的邏輯過期時間(如30分鐘后)
            CacheData&lt;Object&gt; newCacheData = new CacheData<>();
            newCacheData.setData(newData);
            newCacheData.setExpireTime(System.currentTimeMillis() + 30 * 60 * 1000);
            // 更新Redis緩存(無物理過期時間)
            redisTemplate.opsForValue().set(key, newCacheData);
        } catch (Exception e) {
            // 異常處理(如日志記錄),避免異步任務(wù)失敗導(dǎo)致緩存無法更新
            log.error("邏輯過期緩存更新失敗,key:{}", key, e);
        }
    });
    
    // 直接返回舊數(shù)據(jù),不阻塞當(dāng)前請求
    return cacheData.getData();
}

// 模擬數(shù)據(jù)庫查詢方法(與互斥鎖方案一致)
private Object queryDatabase(String key) {
    return "數(shù)據(jù)庫查詢到的" + key + "對應(yīng)數(shù)據(jù)";
}

// 4. 初始化緩存(緩存預(yù)熱,存入帶邏輯過期時間的數(shù)據(jù))
public void initCache(String key) {
    Object data = queryDatabase(key);
    CacheData&lt;Object&gt; cacheData = new CacheData<>();
    cacheData.setData(data);
    // 設(shè)置邏輯過期時間(30分鐘)
    cacheData.setExpireTime(System.currentTimeMillis() + 30 * 60 * 1000);
    // 存入Redis,不設(shè)置物理過期時間
    redisTemplate.opsForValue().set(key, cacheData);
}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Python利用redis限制用戶重復(fù)刷新帶來的數(shù)據(jù)問題

    Python利用redis限制用戶重復(fù)刷新帶來的數(shù)據(jù)問題

    在網(wǎng)站開發(fā)中,我們經(jīng)常會遇到需要控制用戶重復(fù)刷新頁面的情況,本文就來介紹了Python利用redis限制用戶重復(fù)刷新帶來的數(shù)據(jù)問題,感興趣的可以了解一下
    2024-03-03
  • Redis基本數(shù)據(jù)類型示例詳解

    Redis基本數(shù)據(jù)類型示例詳解

    本文給大家介紹了Redis基本數(shù)據(jù)類型示例詳解,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-09-09
  • Redis 5.05 單獨模式安裝及配置方法

    Redis 5.05 單獨模式安裝及配置方法

    這篇文章主要介紹了Redis 5.05 單獨模式安裝,文中通過代碼給大家介紹了Redis 5.0.5 單節(jié)點 安裝配置方法,需要的朋友可以參考下
    2019-10-10
  • Redis中大key處理的問題解決

    Redis中大key處理的問題解決

    本文主要介紹了Redis中大key處理的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2026-03-03
  • 利用Redis實現(xiàn)防止接口重復(fù)提交功能

    利用Redis實現(xiàn)防止接口重復(fù)提交功能

    大家好,本篇文章主要講的是利用Redis實現(xiàn)防止接口重復(fù)提交功能,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • Redis官方可視化工具RedisInsight的安裝使用詳細教程(功能強大)

    Redis官方可視化工具RedisInsight的安裝使用詳細教程(功能強大)

    RedisInsight是Redis官方出品的可視化管理工具,可用于設(shè)計、開發(fā)、優(yōu)化你的Redis應(yīng)用。支持深色和淺色兩種主題,界面非常炫酷,接下來通過本文給大家介紹Redis官方可視化工具RedisInsight的安裝使用過程,需要的朋友可以參考下
    2022-04-04
  • 監(jiān)聽Redis實時數(shù)據(jù)變化實現(xiàn)過程

    監(jiān)聽Redis實時數(shù)據(jù)變化實現(xiàn)過程

    本文介紹了通過定時遍歷Redis并對比緩存Map,實現(xiàn)監(jiān)聽指定前綴鍵的value變化的方法,并簡要提及了可用Keyspace通知等更高效的手段
    2025-10-10
  • Redis中的分布式鎖之SETNX底層實現(xiàn)方式

    Redis中的分布式鎖之SETNX底層實現(xiàn)方式

    這篇文章主要介紹了Redis中的分布式鎖之SETNX底層實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • Redis的持久化方案詳解

    Redis的持久化方案詳解

    在本篇文章里小編給大家整理的是關(guān)于Redis的持久化方案詳解,有興趣的朋友們可以參考下。
    2020-03-03
  • Redis高性能的原因及說明

    Redis高性能的原因及說明

    這篇文章主要介紹了Redis高性能的原因及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10

最新評論

巫山县| 汽车| 开江县| 宝鸡市| 罗田县| 巴中市| 宜都市| 长岛县| 青海省| 兴义市| 西丰县| 噶尔县| 恩平市| 德化县| 新安县| 霍州市| 宝应县| 若羌县| 南康市| 固始县| 扶沟县| 施甸县| 华池县| 南城县| 洱源县| 水富县| 达孜县| 许昌市| 丹东市| 育儿| 迭部县| 琼海市| 江口县| 兖州市| 万安县| 崇礼县| 黄梅县| 迁西县| 江西省| 杨浦区| 冕宁县|