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

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

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

1. 緩存擊穿概念

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

如圖所示:

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

2. 解決方案

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

  • 互斥鎖
  • 邏輯過期

2.1 互斥鎖

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

互斥鎖的流程圖如下:

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

優(yōu)點:

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

缺點:

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

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

我們先設定一個場景:假設這是一個電商平臺,我們通過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è)務方法:

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

邏輯過期的流程圖如下:

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

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

優(yōu)點:

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

缺點:

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

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

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

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

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

我們想要實現(xiàn)邏輯過期,首先得清楚redis中到底要存儲什么樣的數(shù)據(jù)?我們是不是要在每個類中都添加一個邏輯過期的字段?這是不對的,如果我們再每個類中都添加了一個邏輯過期時間字段,這樣對原代碼就有了 侵入性 ,我們應該使整個系統(tǒ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);
    //設置邏輯過期時間
    redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

4)編寫業(yè)務方法并調(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分布式鎖

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

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

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

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

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

    mac下設置redis開機啟動方法步驟

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

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

    本文介紹Redis在高并發(fā)分布式系統(tǒng)中的應用及可能引發(fā)的問題,包括緩存穿透、緩存擊穿和緩存雪崩,針對這些問題,提出了布隆過濾器、分布式鎖、隨機過期時間、高可用集群、本地緩存和預熱策略等多種解決方案,通過合理配置和組合使用這些方案,可以有效提升系統(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)因為操作系統(tǒng)內(nèi)存分配機制,外因為Redis操作特性,使用Redis內(nèi)置內(nèi)存碎片清理機制可有效降低碎片率,但需注意可能影響性能,建議使用MEMORY命令診斷內(nèi)存使用情況,合理配置參數(shù)以優(yōu)化性能
    2024-09-09
  • Redis序列化轉(zhuǎn)換類型報錯的解決

    Redis序列化轉(zhuǎn)換類型報錯的解決

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

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

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

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

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

    Spring?redis使用報錯Read?timed?out排查及解決過程

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

最新評論

安远县| 沙河市| 金坛市| 福海县| 松原市| 肥城市| 资兴市| 嘉祥县| 崇义县| 双城市| 青河县| 梧州市| 周宁县| 三亚市| 威海市| 嘉祥县| 台中市| 东方市| 西乌珠穆沁旗| 敖汉旗| 庆城县| 龙南县| 定远县| 孟村| 铜梁县| 泰来县| 缙云县| 横山县| 富顺县| 平遥县| 白银市| 故城县| 武穴市| 德庆县| 县级市| 桂阳县| 四会市| 顺昌县| 丰县| 当涂县| 安达市|