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

Redis解決緩存雪崩、穿透和擊穿的問題(Redis使用必看)

 更新時間:2023年08月15日 09:51:26   作者:我有一顆五葉草  
這篇文章主要給大家介紹了Redis解決緩存雪崩、緩存穿透、緩存擊穿的解決方案,文中有詳細的圖文介紹,具有一定的參考價值,需要的朋友可以參考下

緩存擊穿

緩存擊穿問題也叫熱點Key問題,就是一個被高并發(fā)訪問并且緩存重建業(yè)務(wù)較復(fù)雜的key突然失效了,無數(shù)的請求訪問會在瞬間給數(shù)據(jù)庫帶來巨大的沖擊。常見的解決方案有:

- 互斥鎖
	- 邏輯過期
	- key 永不過期
	- 接口限流

邏輯分析:假設(shè)線程1在查詢緩存之后,本來應(yīng)該去查詢數(shù)據(jù)庫,然后把這個數(shù)據(jù)重新加載到緩存的,此時只要線程1走完這個邏輯,其他線程就都能從緩存中加載這些數(shù)據(jù)了,但是假設(shè)在線程1沒有走完的時候,后續(xù)的線程2,線程3,線程4同時過來訪問當前這個方法, 那么這些線程都不能從緩存中查詢到數(shù)據(jù),那么他們就會同一時刻來訪問查詢緩存,都沒查到,接著同一時間去訪問數(shù)據(jù)庫,同時的去執(zhí)行數(shù)據(jù)庫代碼,對數(shù)據(jù)庫訪問壓力非常大。

解決方案一、使用鎖來解決:

因為鎖能實現(xiàn)互斥性。假設(shè)線程過來,只能一個人一個人的來訪問數(shù)據(jù)庫,從而避免對于數(shù)據(jù)庫訪問壓力過大,但這也會影響查詢的性能,因為此時會讓查詢的性能從并行變成了串行,我們可以采用 tryLock 方法 + double check 來解決這樣的問題。假設(shè)現(xiàn)在線程1過來訪問,他查詢緩存沒有命中,但是此時他獲得到了鎖的資源,那么線程1就會一個人去執(zhí)行邏輯,假設(shè)現(xiàn)在線程2過來,線程2在執(zhí)行過程中,并沒有獲得到鎖,那么線程2就可以進行到休眠,直到線程1把鎖釋放后,線程2獲得到鎖,然后再來執(zhí)行邏輯,此時就能夠從緩存中拿到數(shù)據(jù)了。

1653328288627.png

解決方案二、邏輯過期方案

方案分析:我們之所以會出現(xiàn)這個緩存擊穿問題,主要原因是在于我們對key設(shè)置了過期時間,假設(shè)我們不設(shè)置過期時間,其實就不會有緩存擊穿的問題,但是不設(shè)置過期時間,這樣數(shù)據(jù)不就一直占用我們內(nèi)存了嗎,我們可以采用邏輯過期方案。我們把過期時間設(shè)置在 redis的value中,注意:這個過期時間并不會直接作用于redis,而是我們后續(xù)通過邏輯去處理。假設(shè)線程1去查詢緩存,然后從value中判斷出來當前的數(shù)據(jù)已經(jīng)過期了,此時線程1去獲得互斥鎖,那么其他線程會進行阻塞,獲得了鎖的線程他會開啟一個 線程去進行 以前的重構(gòu)數(shù)據(jù)的邏輯,直到新開的線程完成這個邏輯后,才釋放鎖, 而線程1直接進行返回,假設(shè)現(xiàn)在線程3過來訪問,由于線程線程2持有著鎖,所以線程3無法獲得鎖,線程3也直接返回數(shù)據(jù),只有等到新開的線程2把重建數(shù)據(jù)構(gòu)建完后,其他線程才能走返回正確的數(shù)據(jù)。這種方案巧妙在于,異步的構(gòu)建緩存,缺點在于在構(gòu)建完緩存之前,返回的都是臟數(shù)據(jù)。

1653328663897.png

進行對比互斥鎖方案由于保證了互斥性,所以數(shù)據(jù)一致,且實現(xiàn)簡單,因為僅僅只需要加一把鎖而已,也沒其他的事情需要操心,所以沒有額外的內(nèi)存消耗,缺點在于有鎖就有死鎖問題的發(fā)生,且只能串行執(zhí)行性能肯定受到影響邏輯過期方案: 線程讀取過程中不需要等待,性能好,有一個額外的線程持有鎖去進行重構(gòu)數(shù)據(jù),但是在重構(gòu)數(shù)據(jù)完成前,其他的線程只能返回之前的數(shù)據(jù),且實現(xiàn)起來麻煩。

1653357522914.png

解決方案三、永不過期 主動更新

解決方案四、接口限流

利用互斥鎖解決緩存擊穿問題

核心思路:相較于原來從緩存中查詢不到數(shù)據(jù)后直接查詢數(shù)據(jù)庫而言,現(xiàn)在的方案是 進行查詢之后,如果從緩存沒有查詢到數(shù)據(jù),則進行互斥鎖的獲取,獲取互斥鎖后,判斷是否獲得到了鎖,如果沒有獲得到,則休眠,過一會再進行嘗試,直到獲取到鎖為止,才能進行查詢?nèi)绻@取到了鎖的線程,再去進行查詢,查詢后將數(shù)據(jù)寫入redis,再釋放鎖,返回數(shù)據(jù),利用互斥鎖就能保證只有一個線程去執(zhí)行操作數(shù)據(jù)庫的邏輯,防止緩存擊穿。

1653357860001.png

操作鎖的代碼:核心思路就是利用 redis 的 setnx 方法來表示獲取鎖,該方法含義是redis中如果沒有這個 key,則插入成功,返回1,在 stringRedisTemplate 中返回 true, 如果有這個 key 則插入失敗,則返回0,在stringRedisTemplate 返回 false,我們可以通過 true,或者是 false,來表示是否有線程成功插入 key,成功插入的 key 的線程我們認為他就是獲得到鎖的線程。

private boolean tryLock(String key, String value, long time) {
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}
private void unlock(String key) {
    stringRedisTemplate.delete(key);
}

操作鎖要注意對 value 加入標識,在釋放鎖之前對其進行判斷是不是自己的鎖,防止誤刪?。ㄟ€要保證判斷語句和釋放語句的原子性 可以用 lua 腳本)

核心代碼:

public Shop queryWithMutex(Long id)  {
    String key = CACHE_SHOP_KEY + id;
    // 1、從redis中查詢商鋪緩存
    String shopJson = stringRedisTemplate.opsForValue().get("key");
    // 2、判斷是否存在
    if (StrUtil.isNotBlank(shopJson)) {
        // 存在,直接返回
        return JSONUtil.toBean(shopJson, Shop.class);
    }
    //判斷命中的值是否是空值
    if (shopJson != null) {
        //返回一個錯誤信息
        return null;
    }
    // 4.實現(xiàn)緩存重構(gòu)
    //4.1 獲取互斥鎖
    String lockKey = "lock:shop:" + id;
	long current_thread_id = Thread.currentThread().getId();
    Shop shop = null;
    try {
        boolean isLock = tryLock(lockKey, current_thread_id, 10);
        // 4.2 判斷否獲取成功
        if(!isLock){
            //4.3 失敗,則休眠重試
            Thread.sleep(50);
            return queryWithMutex(id);
        }
        //4.4 成功,根據(jù)id查詢數(shù)據(jù)庫
         shop = getById(id);
        // 5.不存在,返回錯誤
        if(shop == null){
             //將空值寫入redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            //返回錯誤信息
            return null;
        }
        //6.寫入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_NULL_TTL,TimeUnit.MINUTES);
    }catch (Exception e){
        throw new RuntimeException(e);
    }
    finally {
        //7.釋放互斥鎖
        Object o = stringRedisTemplate.opsForValue().get(key);
        if(o != null && (String)o.equals(current_thread_id)){
            unlock(lockKey);
        }
    }
    return shop;
}

利用邏輯過期解決緩存擊穿問題

緩存穿透

緩存穿透 :緩存穿透是指客戶端請求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫中都不存在,這樣緩存永遠不會生效,這些請求都會打到數(shù)據(jù)庫。

常見的解決方案有以下幾種:

  • 緩存空對象
    • 優(yōu)點:實現(xiàn)簡單,維護方便
    • 缺點:
      • 額外的內(nèi)存消耗
      • 可能造成短期的不一致
  • 布隆過濾
    • 優(yōu)點:內(nèi)存占用較少,沒有多余key
    • 缺點:
      • 實現(xiàn)復(fù)雜
      • 存在誤判可能
  • id 格式校驗

緩存空對象

思路分析

當我們客戶端訪問不存在的數(shù)據(jù)時,先請求redis,但是此時redis中沒有數(shù)據(jù),此時會訪問到數(shù)據(jù)庫,但是數(shù)據(jù)庫中也沒有數(shù)據(jù),這個數(shù)據(jù)穿透了緩存,直擊數(shù)據(jù)庫。因為數(shù)據(jù)庫能夠承載的并發(fā)不如 redis 這么高,如果大量的請求同時過來訪問這種不存在的數(shù)據(jù),這些請求就都會訪問到數(shù)據(jù)庫,簡單的解決方案就是哪怕這個數(shù)據(jù)在數(shù)據(jù)庫中也不存在,我們也把這個數(shù)據(jù)存入到 redis 中去,這樣,下次用戶過來訪問這個不存在的數(shù)據(jù),那么在redis中也能找到這個數(shù)據(jù)就不會進入到緩存了。

布隆過濾

我們可以將數(shù)據(jù)庫的數(shù)據(jù),所對應(yīng)的id寫入到一個list集合中,當用戶過來訪問的時候,我們直接去判斷l(xiāng)ist中是否包含當前的要查詢的數(shù)據(jù),如果說用戶要查詢的id數(shù)據(jù)并不在list集合中,則直接返回,如果list中包含對應(yīng)查詢的id數(shù)據(jù),則說明不是一次緩存穿透數(shù)據(jù),則直接放行。

1653836416586.png

現(xiàn)在的問題是這個主鍵其實并沒有那么短,而是很長的一個 主鍵哪怕你單獨去提取這個主鍵,但是在11年左右,淘寶的商品總量就已經(jīng)超過10億個所以如果采用以上方案,這個list也會很大,所以我們可以使用bitmap來減少list的存儲空間我們可以把list數(shù)據(jù)抽象成一個非常大的bitmap,我們不再使用list,而是將db中的id數(shù)據(jù)利用哈希思想,比如:id % bitmap.size = 算出當前這個id對應(yīng)應(yīng)該落在bitmap的哪個索引上,然后將這個值從0變成1,然后當用戶來查詢數(shù)據(jù)時,此時已經(jīng)沒有了list,讓用戶用他查詢的id去用相同的哈希算法, 算出來當前這個id應(yīng)當落在bitmap的哪一位,然后判斷這一位是0,還是1,如果是0則表明這一位上的數(shù)據(jù)一定不存在, 采用這種方式來處理,需要重點考慮一個事情,就是誤差率,所謂的誤差率就是指當發(fā)生哈希沖突的時候,產(chǎn)生的誤差。

1653836578970.png

id 格式校驗

將客戶端傳來的 id 做校驗比如:

if(id < 1 || id > Integer.MIN_VALUE){
	return null;
}

具體校驗根據(jù)業(yè)務(wù)來。

緩存雪崩

緩存雪崩是指在同一時段大量的緩存key同時失效或者Redis服務(wù)宕機,導(dǎo)致大量請求到達數(shù)據(jù)庫,帶來巨大壓力。

緩存雪崩

解決方案

  • 給不同的Key的TTL添加隨機值
  • 利用Redis集群提高服務(wù)的可用性
  • 給緩存業(yè)務(wù)添加降級限流策略
  • 給業(yè)務(wù)添加多級緩存

以上就是Redis解決緩存雪崩、穿透和擊穿的問題(Redis使用必看)的詳細內(nèi)容,更多關(guān)于Redis解決緩存雪崩、穿透和擊穿的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Redis的KEYS 命令千萬不能亂用

    Redis的KEYS 命令千萬不能亂用

    這篇文章主要介紹了Redis的KEYS 命令千萬不能亂用,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • Redis中Zset類型常用命令的實現(xiàn)

    Redis中Zset類型常用命令的實現(xiàn)

    Zset是Redis的一種有序集合數(shù)據(jù)類型,Zset通過壓縮列表和跳躍表兩種底層編碼方式支持小數(shù)據(jù)集和大數(shù)據(jù)集,支持多種操作,包括添加、查詢、刪除元素以及集合運算等,具有不同的時間復(fù)雜度,感興趣的可以了解一下
    2024-10-10
  • 基于Redis驗證碼發(fā)送及校驗方案實現(xiàn)

    基于Redis驗證碼發(fā)送及校驗方案實現(xiàn)

    本文主要介紹了基于Redis驗證碼發(fā)送及校驗方案實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-01-01
  • 關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹

    關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹

    大家好,本篇文章主要講的是關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • Redis實現(xiàn)分布式Session管理的機制詳解

    Redis實現(xiàn)分布式Session管理的機制詳解

    這篇文章主要介紹了Redis實現(xiàn)分布式Session管理的機制詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • 如何監(jiān)聽Redis中Key值的變化(SpringBoot整合)

    如何監(jiān)聽Redis中Key值的變化(SpringBoot整合)

    測試過程中我們有一部分常量值放入redis,共大部分應(yīng)用調(diào)用,但在測試過程中經(jīng)常有人會清空redis,回歸測試,下面這篇文章主要給大家介紹了關(guān)于如何監(jiān)聽Redis中Key值變化的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • React?組件的常用生命周期函數(shù)匯總

    React?組件的常用生命周期函數(shù)匯總

    這篇文章主要介紹了React?組件的常用生命周期函數(shù)匯總,組件的生命周期有助于理解組件的運行方式、完成更復(fù)雜的組件功能、分析組件錯誤原因等
    2022-08-08
  • redis不能訪問本機真實ip地址的解決方案

    redis不能訪問本機真實ip地址的解決方案

    這篇文章主要介紹了redis不能訪問本機真實ip地址的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 利用Supervisor管理Redis進程的方法教程

    利用Supervisor管理Redis進程的方法教程

    Supervisor 是可以在類 UNIX 系統(tǒng)中進行管理和監(jiān)控各種進程的小型系統(tǒng)。它自帶了客戶端和服務(wù)端工具,下面這篇文章主要給大家介紹了關(guān)于利用Supervisor管理Redis進程的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-08-08
  • 聊聊使用RedisTemplat實現(xiàn)簡單的分布式鎖的問題

    聊聊使用RedisTemplat實現(xiàn)簡單的分布式鎖的問題

    這篇文章主要介紹了使用RedisTemplat實現(xiàn)簡單的分布式鎖問題,文中給大家介紹在SpringBootTest中編寫測試模塊的詳細代碼,需要的朋友可以參考下
    2021-11-11

最新評論