欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis 緩存擊穿問題及解決方案

 更新時間:2023年12月19日 09:20:33   作者:嗯mua.  
緩存擊穿是指在高并發(fā)環(huán)境下,大量請求同時訪問緩存中不存在的數(shù)據(jù),導(dǎo)致這些請求穿透到數(shù)據(jù)庫,本文主要介紹了Redis緩存擊穿問題及解決方案

1. 緩存擊穿概念

緩存擊穿:緩存擊穿也叫做熱點Key問題,就是少量被高并發(fā)訪問并且緩存重建業(yè)務(wù)比較復(fù)雜的key突然失效了,無數(shù)的請求訪問會在瞬間給數(shù)據(jù)庫帶來巨大的壓力。

如圖所示:

線程1緩存未命中,去重建緩存;在線程1重建緩存的時候,線程2緩存又沒命中,線程2也去重建緩存;和線程2同時來的線程3,線程4…緩存都沒命中,都去重建緩存,給數(shù)據(jù)庫帶來了巨大的壓力。

2. 解決方案

緩存擊穿的常見解決方案有兩種:

  • 互斥鎖
  • 邏輯過期

2.1 互斥鎖

互斥鎖的實現(xiàn)思路就是在第一個線程到來的時候獲取互斥鎖,后面的線程來到之后嘗試去獲取互斥鎖,獲取失敗,于是進(jìn)行休眠重試。直到第一個線程緩存重建成功之后,釋放互斥鎖。之后其余線程在重試過程中就成功查詢緩存命中了重建數(shù)據(jù)。

互斥鎖的流程圖如下:

2.1.1 互斥鎖的優(yōu)缺點

優(yōu)點:

  • 沒有額外的內(nèi)存消耗
  • 保證一致性(數(shù)據(jù)庫和redis數(shù)據(jù)一致)
  • 實現(xiàn)簡單

缺點:

  • 線程需要等待,性能受影響
  • 可能有死鎖風(fēng)險(一個方法里有多個查詢操作,另一個方法也有多個重合的查詢操作)

2.1.2 互斥鎖的代碼實現(xiàn)

我們先設(shè)定一個場景:假設(shè)這是一個電商平臺,我們通過id去查詢店鋪信息。

代碼實現(xiàn)流程圖如下:

首先我們編寫獲取鎖和釋放鎖的方法,如下所示:

//獲取鎖
private boolean tryLock(String key) {
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}

//釋放鎖
private void unLock(String key) {
    stringRedisTemplate.delete(key);
}

然后編寫一個解決緩存擊穿問題的方法,最后寫一個調(diào)用解決方法的業(yè)務(wù)方法:

@Override
public Result queryById(Long id) {
    //緩存空對象解決 緩存穿透
    //Shop shop = queryWithPassThrough(id);

    //互斥鎖解決 緩存擊穿
    Shop shop = queryWithMutex(id);
    if (shop == null) {
        return Result.fail("店鋪不存在!");
    }
    return Result.ok(shop);
}

public Shop queryWithMutex(Long id) {
    //1.從redis查詢商鋪緩存
    String key = CACHE_SHOP_KEY + id;
    String shopJson = stringRedisTemplate.opsForValue().get(key);
    //2.判斷是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        //3.存在,直接返回
        return JSONUtil.toBean(shopJson, Shop.class);
    }
    //此時 shopJson 不是為null就是為""
    if (shopJson != null) {
        //為""直接返回錯誤信息,為null查詢數(shù)據(jù)庫
        return null;
    }

    //4.實現(xiàn)緩存重建
    //4.1.獲取互斥鎖
    String lockKey = "lock:shop:" + id;
    Shop shop = null;
    try {
        boolean isLock = tryLock(lockKey);
        //4.2.判斷是否獲取成功
        while (!isLock) {
            //4.3.失敗,則休眠重試
            Thread.sleep(50);
            return queryWithMutex(id);
        }
        //4.4.獲取鎖成功,再次檢測緩存釋放存在(double check)
        String cacheShopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(cacheShopJson)) {
            //4.5.存在,直接返回
            return JSONUtil.toBean(cacheShopJson, Shop.class);
        }
        //5.緩存數(shù)據(jù)不存在,根據(jù)id查詢數(shù)據(jù)庫
        shop = getById(id);
        //模擬重建的延時
        Thread.sleep(200);
        //6.不存在,返回錯誤
        if (shop == null) {
            //緩存空值
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        //7.存在,寫入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        //8.釋放鎖
        unLock(lockKey);
    }
    return shop;
}

2.2 邏輯過期

邏輯過期就是給緩存的數(shù)據(jù)添加一個邏輯過期字段,而不是真正的給它設(shè)置一個TTL。每次查詢緩存的時候去判斷是否已經(jīng)超過了我們設(shè)置的邏輯過期時間,如果未過期,直接返回緩存數(shù)據(jù);如果已經(jīng)過期則進(jìn)行緩存重建。

邏輯過期的流程圖如下:

解釋:第一個線程到來之后發(fā)現(xiàn)邏輯過期,于是獲取互斥鎖,再開啟一個新線程去進(jìn)行緩存重建。當(dāng)后續(xù)線程到來時,發(fā)現(xiàn)緩存已過期,嘗試獲取互斥鎖也失敗,但是此時不進(jìn)行等待重試,而是直接返回過期數(shù)據(jù)。之后第一個線程成功緩存數(shù)據(jù)釋放互斥鎖之后,后面線程繼續(xù)來訪,發(fā)現(xiàn)命中緩存并且沒有過期,返回重建數(shù)據(jù)。

2.2.1 邏輯過期的優(yōu)缺點

優(yōu)點:

  • 線程無需等待,性能較好

缺點:

  • 不保證一致性(因為會返回過期數(shù)據(jù))
  • 有額外的內(nèi)存消耗(同時緩存了邏輯過期時間的字段)
  • 實現(xiàn)復(fù)雜

2.2.2 邏輯過期的代碼實現(xiàn)

我們先設(shè)定一個場景:假設(shè)這是一個電商平臺,我們通過id去查詢店鋪信息。

代碼實現(xiàn)流程圖如下:

1)構(gòu)建存儲類

我們想要實現(xiàn)邏輯過期,首先得清楚redis中到底要存儲什么樣的數(shù)據(jù)?我們是不是要在每個類中都添加一個邏輯過期的字段?這是不對的,如果我們再每個類中都添加了一個邏輯過期時間字段,這樣對原代碼就有了 侵入性 ,我們應(yīng)該使整個系統(tǒng)具有可拓展性,所以我們應(yīng)該新建一個類來填充要存入redis的數(shù)據(jù),代碼如下:

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

2)創(chuàng)建線程池

由于我們需要開啟獨立線程去重建緩存,所以我們可以選擇創(chuàng)建一個線程池。

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

3)編寫緩存重建的代碼

緩存重建就是直接查詢數(shù)據(jù)庫,將查詢到的數(shù)據(jù)緩存到redis中。

public void saveShop2Redis(Long id, Long expireSeconds) throws InterruptedException {
    //1.查詢店鋪數(shù)據(jù)
    Shop shop = getById(id);
    //2.封裝邏輯過期時間
    RedisData redisData = new RedisData();
    redisData.setData(shop);
    //設(shè)置邏輯過期時間
    redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

4)編寫業(yè)務(wù)方法并調(diào)用緩存擊穿方法

@Override
public Result queryById(Long id) {
    //緩存空對象解決 緩存穿透
    //Shop shop = queryWithPassThrough(id);

    //互斥鎖解決 緩存擊穿
    //Shop shop = queryWithMutex(id);

    //邏輯過期解決 緩存擊穿
    Shop shop = queryWithLogicalExpire(id);
    if (shop == null) {
        return Result.fail("店鋪不存在!");
    }
    return Result.ok(shop);
}

public Shop queryWithLogicalExpire(Long id) {
    //1.從redis查詢商鋪緩存
    String key = CACHE_SHOP_KEY + id;
    String shopJson = stringRedisTemplate.opsForValue().get(key);
    //2.判斷是否存在
    if (StrUtil.isBlank(shopJson)) {
        //未命中,直接返回空
        return null;
    }
    //3.命中,判斷是否過期
    RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
    Shop cacheShop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
    if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
        //3.1未過期,直接返回店鋪信息
        return cacheShop;
    }
    //3.2.已過期,緩存重建
    //3.3.獲取鎖
    String lockKey = LOCK_SHOP_KEY + id;
    boolean flag = tryLock(lockKey);
    if (flag) {
        //3.4.獲取成功
        //4再次檢查redis緩存是否過期,做double check
        shopJson = stringRedisTemplate.opsForValue().get(key);
        //4.1.判斷是否存在
        if (StrUtil.isBlank(shopJson)) {
            //未命中,直接返回空
            return null;
        }
        //4.2.命中,判斷是否過期
        redisData = JSONUtil.toBean(shopJson, RedisData.class);
        cacheShop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
            //4.3.未過期,直接返回店鋪信息
            return cacheShop;
        }
        CACHE_REBUILD_EXECUTOR.submit(() -> {
            //5.重建緩存
            try {
                this.saveShop2Redis(id, 20L);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //釋放鎖
                unLock(lockKey);
            }
        });
    }
    //7.獲取失敗,返回舊數(shù)據(jù)
    return cacheShop;
}

到此這篇關(guān)于Redis 緩存擊穿問題及解決方案的文章就介紹到這了,更多相關(guān)Redis 緩存擊穿內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • Redis緩存雪崩的物種解決方案

    Redis緩存雪崩的物種解決方案

    在高并發(fā)系統(tǒng)中,Redis作為核心緩存組件,通常扮演著重要的"守門員"角色,當(dāng)大量緩存同時失效時,會導(dǎo)致請求如洪水般直接涌向數(shù)據(jù)庫,造成數(shù)據(jù)庫瞬間壓力劇增甚至宕機,這種現(xiàn)象被形象地稱為"緩存雪崩",本文給大家介紹了Redis緩存雪崩的5種應(yīng)對措施,需要的朋友可以參考下
    2025-04-04
  • Redis集群Lettuce主從切換問題解決方案

    Redis集群Lettuce主從切換問題解決方案

    這篇文章主要為大家介紹了Redis集群Lettuce主從切換問題解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Redis 哨兵與集群腦裂問題及其解決

    Redis 哨兵與集群腦裂問題及其解決

    本文主要介紹了Redis 哨兵與集群腦裂問題及其解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • Redis源碼閱讀:Redis字符串SDS詳解

    Redis源碼閱讀:Redis字符串SDS詳解

    這篇文章主要介紹了Redis源碼閱讀:Redis字符串SDS,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Redis如何一鍵部署腳本

    Redis如何一鍵部署腳本

    這篇文章主要介紹了Redis如何一鍵部署腳本,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • 淺談Redis 緩存的三大問題及其解決方案

    淺談Redis 緩存的三大問題及其解決方案

    Redis 經(jīng)常用于系統(tǒng)中的緩存,這樣可以解決目前 IO 設(shè)備無法滿足互聯(lián)網(wǎng)應(yīng)用海量的讀寫請求的問題。本文主要介紹了淺談Redis 緩存的三大問題及其解決方案,感興趣的可以了解一下
    2021-07-07
  • Redis分布式鎖之紅鎖的實現(xiàn)

    Redis分布式鎖之紅鎖的實現(xiàn)

    本文主要介紹了Redis分布式鎖之紅鎖的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Redis緩存和數(shù)據(jù)庫的數(shù)據(jù)一致性的問題解決

    Redis緩存和數(shù)據(jù)庫的數(shù)據(jù)一致性的問題解決

    隨業(yè)務(wù)增長,直接操作數(shù)據(jù)庫性能下降,引入緩存提高讀性能常見,但緩存和數(shù)據(jù)庫的雙寫操作會引發(fā)數(shù)據(jù)不一致問題,本文討論幾種常用同步策略,感興趣的可以了解一下
    2024-09-09
  • 關(guān)于Redis庫存超賣問題的分析

    關(guān)于Redis庫存超賣問題的分析

    在高并發(fā)場景下進(jìn)行優(yōu)惠券秒殺測試時,發(fā)現(xiàn)由于并發(fā)操作導(dǎo)致了超賣問題,即理論上只能賣出100個優(yōu)惠券,實際賣出了102個,分析原因,是因為在高并發(fā)環(huán)境下,多個線程同時操作庫存,導(dǎo)致數(shù)據(jù)不一致,提出了兩種解決方案:悲觀鎖和樂觀鎖
    2024-11-11
  • jedis配置含義詳解

    jedis配置含義詳解

    這篇文章主要介紹了jedis配置含義詳解的相關(guān)資料,需要的朋友可以參考下
    2020-04-04

最新評論