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

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

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

1. 緩存擊穿概念

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

如圖所示:

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

2. 解決方案

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

  • 互斥鎖
  • 邏輯過期

2.1 互斥鎖

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

互斥鎖的流程圖如下:

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

優(yōu)點(diǎn):

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

缺點(diǎn):

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

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

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

代碼實(shí)現(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);
}

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

@Override
public Result queryById(Long id) {
    //緩存空對(duì)象解決 緩存穿透
    //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);
    }
    //此時(shí) shopJson 不是為null就是為""
    if (shopJson != null) {
        //為""直接返回錯(cuò)誤信息,為null查詢數(shù)據(jù)庫
        return null;
    }

    //4.實(shí)現(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);
        //模擬重建的延時(shí)
        Thread.sleep(200);
        //6.不存在,返回錯(cuò)誤
        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ù)添加一個(gè)邏輯過期字段,而不是真正的給它設(shè)置一個(gè)TTL。每次查詢緩存的時(shí)候去判斷是否已經(jīng)超過了我們?cè)O(shè)置的邏輯過期時(shí)間,如果未過期,直接返回緩存數(shù)據(jù);如果已經(jīng)過期則進(jìn)行緩存重建。

邏輯過期的流程圖如下:

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

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

優(yōu)點(diǎn):

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

缺點(diǎn):

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

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

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

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

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

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

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

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

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

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.封裝邏輯過期時(shí)間
    RedisData redisData = new RedisData();
    redisData.setData(shop);
    //設(shè)置邏輯過期時(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) {
    //緩存空對(duì)象解決 緩存穿透
    //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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • 淺析Redis分布式鎖

    淺析Redis分布式鎖

    本篇文章通過實(shí)例給大家講解了Redis分布式鎖工作原理以及用法分享,有需要的朋友參考學(xué)習(xí)下吧。
    2017-12-12
  • Redis列表類型的常用命令小結(jié)

    Redis列表類型的常用命令小結(jié)

    這篇文章給大家整理了在操作Redis列表類型中的常用命令,文章總結(jié)的很全面,對(duì)大家學(xué)習(xí)Redis具有一定的參考借鑒價(jià)值,下面來一起看看吧。
    2016-09-09
  • 如何通過redis減庫存的秒殺場景實(shí)現(xiàn)

    如何通過redis減庫存的秒殺場景實(shí)現(xiàn)

    本文通過解決秒殺系統(tǒng)中的一個(gè)場景即數(shù)據(jù)預(yù)加載,即把庫存數(shù)據(jù)事先加載到緩存,然后通過緩存來更新庫存,簡單介紹了如何通過redis減庫存的秒殺場景實(shí)現(xiàn),感興趣的可以了解一下
    2022-06-06
  • mac下設(shè)置redis開機(jī)啟動(dòng)方法步驟

    mac下設(shè)置redis開機(jī)啟動(dòng)方法步驟

    這篇文章主要介紹了mac下設(shè)置redis開機(jī)啟動(dòng),本文詳細(xì)的給出了操作步驟,需要的朋友可以參考下
    2015-07-07
  • Redis緩存穿透、緩存擊穿與緩存雪崩實(shí)戰(zhàn)指南

    Redis緩存穿透、緩存擊穿與緩存雪崩實(shí)戰(zhàn)指南

    本文介紹Redis在高并發(fā)分布式系統(tǒng)中的應(yīng)用及可能引發(fā)的問題,包括緩存穿透、緩存擊穿和緩存雪崩,針對(duì)這些問題,提出了布隆過濾器、分布式鎖、隨機(jī)過期時(shí)間、高可用集群、本地緩存和預(yù)熱策略等多種解決方案,通過合理配置和組合使用這些方案,可以有效提升系統(tǒng)的穩(wěn)定性和性能
    2026-05-05
  • Redis內(nèi)存碎片率調(diào)優(yōu)處理方式

    Redis內(nèi)存碎片率調(diào)優(yōu)處理方式

    Redis集群因內(nèi)存碎片率超過1.5觸發(fā)告警,分析發(fā)現(xiàn)內(nèi)因與外因?qū)е聝?nèi)存碎片,內(nèi)因?yàn)椴僮飨到y(tǒng)內(nèi)存分配機(jī)制,外因?yàn)镽edis操作特性,使用Redis內(nèi)置內(nèi)存碎片清理機(jī)制可有效降低碎片率,但需注意可能影響性能,建議使用MEMORY命令診斷內(nèi)存使用情況,合理配置參數(shù)以優(yōu)化性能
    2024-09-09
  • Redis序列化轉(zhuǎn)換類型報(bào)錯(cuò)的解決

    Redis序列化轉(zhuǎn)換類型報(bào)錯(cuò)的解決

    本文主要介紹了Redis序列化轉(zhuǎn)換類型報(bào)錯(cuò)的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版)

    基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版)

    這篇文章主要介紹了基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-05-05
  • redis數(shù)據(jù)結(jié)構(gòu)之intset的實(shí)例詳解

    redis數(shù)據(jù)結(jié)構(gòu)之intset的實(shí)例詳解

    這篇文章主要介紹了redis數(shù)據(jù)結(jié)構(gòu)之intset的實(shí)例詳解的相關(guān)資料, intset也即整數(shù)集合,當(dāng)集合保存的值數(shù)量不多時(shí),redis使用intset作為其底層數(shù)據(jù)保存結(jié)構(gòu),希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • Spring?redis使用報(bào)錯(cuò)Read?timed?out排查及解決過程

    Spring?redis使用報(bào)錯(cuò)Read?timed?out排查及解決過程

    項(xiàng)目使用spring集成redis,偶爾會(huì)出現(xiàn)read timed out的情況,剛開始以為是網(wǎng)絡(luò)不穩(wěn)定引起的,后面發(fā)現(xiàn)影響業(yè)務(wù)測試的準(zhǔn)確性,這篇文章主要給大家介紹了關(guān)于Spring redis使用報(bào)錯(cuò)Read timed out排查及解決過程的相關(guān)資料,需要的朋友可以參考下
    2024-02-02

最新評(píng)論

静海县| 安顺市| 商洛市| 谢通门县| 丰都县| 临澧县| 长子县| 阳江市| 苍溪县| 普陀区| 惠水县| 三都| 太和县| 沙雅县| 芜湖市| 荣昌县| 道真| 洱源县| 延寿县| 鹤峰县| 长顺县| 邵阳市| 新化县| 获嘉县| 隆安县| 噶尔县| 武宁县| 郧西县| 宾阳县| 高台县| 乌苏市| 太白县| 定陶县| 高邮市| 河津市| 宁河县| 信宜市| 云霄县| 赤峰市| 探索| 阜城县|