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

Redis高并發(fā)緩存問題分析及解決過程

 更新時間:2025年01月10日 09:48:06   作者:一條行走的魚  
文章總結了Redis緩存的六種常見問題及其解決方案:緩存穿透、緩存擊穿、緩存雪崩、熱點key重建優(yōu)化、緩存和數(shù)據(jù)庫雙寫不一致,以及Redis對過期key的三種清除策略,每種問題都提供了詳細的原因分析和具體的解決方案

Redis緩存問題解決方案

1.緩存穿透

1)什么是緩存穿透

緩存穿透是指查詢一個根本不存在的數(shù)據(jù),緩存層和存儲層都不會命中,通常出于容錯的考慮,如果從存儲層查不到數(shù)據(jù)則不寫入緩存層。

緩存穿透將導致不存在的數(shù)據(jù)每次請求都要到存儲層去查詢, 失去了緩存保護后端存儲的意義。

2)造成緩存穿透的原因

1.自身業(yè)務出現(xiàn)問題或者數(shù)據(jù)有問題。

2.黑客攻擊,制造大量不存在的key 利用壓測工具等進行攻擊

3)解決方案

1.緩存空對象

String get(String key){
    //先從緩存中拿數(shù)據(jù)
    String cacheValue = cache.get(key);
    // 緩存為空
    if (StringUtils.isBlank(cacheValue)) {
        //從db中拿
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        //設置一個過期時間,否則緩存中有大量的空對象
        if (storageValue == null) { 
            cache.expire(key, 60 * 5); 12
        }
        return storageValue;
        
    }else{
        //緩存中可以獲取之間返回
        return cacheValue;
    }
}

2.布隆過濾器

對于惡意攻擊,向服務器請求大量不存在的數(shù)據(jù)造成的緩存穿透,還可以用布隆過濾器先做一次過濾,對于不 存在的數(shù)據(jù)布隆過濾器一般都能夠過濾掉,不讓請求再往后端發(fā)送。

當布隆過濾器說某個值存在時,這個值可 能不存在;當它說不存在時,那就肯定不存在

布隆過濾器就是一個大型的位數(shù)組和幾個不一樣的無偏 hash 函數(shù)。所謂無偏就是能夠把元素的 hash 值算得比較均勻。

向布隆過濾器中添加 key 時,會使用多個 hash 函數(shù)對 key 進行 hash 算得一個整數(shù)索引值然后對位數(shù)組長度進行取模運算得到一個位置,每個 hash 函數(shù)都會算得一個不同的位置。再把位數(shù)組的這幾個位置都置為1就完成了 add 操作。

向布隆過濾器詢問 key 是否存在時,跟 add 一樣,也會把 hash 的幾個位置都算出來,看看位數(shù)組中這幾個位置是否都為 1,只要有一個位為 0,那么說明布隆過濾器中這個key 不存在。如果都是 1,這并不能說明這個 key 就一定存在,只是極有可能存在,因為這些位被置為1可能是因為其它的 key 存在所致。如果這個位數(shù)組比較稀疏,這個概率就會很大,如果這個位數(shù)組比較擁擠,這個概率就會降低。 這種方法適用于數(shù)據(jù)命中不高、 數(shù)據(jù)相對固定、 實時性低(通常是數(shù)據(jù)集較大) 的應用場景, 代碼維護較為 復雜, 但是緩存空間占用很少。

可以用redisson實現(xiàn)布隆過濾器,引入依賴:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>    

實例代碼

package com.redisson;
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedissonBloomFilter{
    
    public static void main(String[] args){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        //構造Redisson
        RedissonClient redisson = Redisson.create(config);
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
        //初始化布隆過濾器:預計元素為100000000L,誤差率為3%,根據(jù)這兩個參數(shù)會計算出底層的bit數(shù)組大小
        bloomFilter.tryInit(100000000L,0.03);
        //將zhuge插入到布隆過濾器中
        bloomFilter.add("zhuge");
        //判斷下面號碼是否在布隆過濾器中
        System.out.println(bloomFilter.contains("123"));//false
        System.out.println(bloomFilter.contains("baiqi"));//false
        System.out.println(bloomFilter.contains("qianyue"));//true
    }
    
}

使用布隆過濾器需要把所有數(shù)據(jù)提前放入布隆過濾器,并且在增加數(shù)據(jù)時也要往布隆過濾器里放,布隆過濾器 緩存過濾偽代碼:

//初始化布隆過濾器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
//初始化布隆過濾器:預計元素為100000000L,誤差率為3%
bloomFilter.tryInit(100000000L,0.03);
//把所有數(shù)據(jù)存入布隆過濾器
void init(){
    for (String key: keys) {
        bloomFilter.put(key);
    }
}
String get(String key){
    // 從布隆過濾器這一級緩存判斷下key是否存在
    Boolean exist = bloomFilter.contains(key);
    if(!exist){
        return "";
    }
    // 從緩存中獲取數(shù)據(jù)
    String cacheValue = cache.get(key);
    // 緩存為空
    if (StringUtils.isBlank(cacheValue)){
        // 從db中獲取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        // 如果存儲數(shù)據(jù)為空, 需要設置一個過期時間(300秒)
        if (storageValue == null){
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    }else{
        return cacheValue;
    }
    
}

注意:布隆過濾器不能刪除數(shù)據(jù),如果要刪除得重新初始化數(shù)據(jù)。

2.緩存擊穿

1)什么是緩存擊穿

由于大批量緩存在同一時間失效可能導致大量請求同時穿透緩存直達數(shù)據(jù)庫,可能會造成數(shù)據(jù)庫瞬間壓力過大甚至掛掉。

2)解決方案

在批量增加緩存時最好將這一批數(shù)據(jù)的緩存過期時間設置為一個時間段內(nèi)的不同時間。

2.緩存雪崩

1)什么是緩存雪崩

緩存雪崩指的是緩存層支撐不住或宕掉后,流量會像奔逃的野牛一樣,打向后端存儲層。 由于緩存層承載著大量請求, 有效地保護了存儲層,但是如果緩存層由于某些原因不能提供服務(比如超大并發(fā)過來,緩存層支撐不住,或者由于緩存設計不好,類似大量請求訪問bigkey,導致緩存能支撐的并發(fā)急劇下降),于是大量請求都會打到存儲層,存儲層的調用量會暴增.造成存儲層也會級聯(lián)宕機的情況。

2)解決方案

1.保證緩存層服務高可用性,比如使用哨兵模式和集群

2.依賴隔離組件為后端限流熔斷并降級。比如使用Sentinel或Hystrix限流降級組件。比如服務降級,我們可以針對不同的數(shù)據(jù)采取不同的處理方式。當業(yè)務應用訪問的是非核心數(shù)據(jù)(例如電商商 品屬性,用戶信息等)時,暫時停止從緩存中查詢這些數(shù)據(jù),而是直接返回預定義的默認降級信息、空值或是 錯誤提示信息;當業(yè)務應用訪問的是核心數(shù)據(jù)(例如電商商品庫存)時,仍然允許查詢緩存,如果緩存缺失,也可以繼續(xù)通過數(shù)據(jù)庫讀取.

4.熱點緩存key重建優(yōu)化

開發(fā)人員使用“緩存+過期時間”的策略既可以加速數(shù)據(jù)讀寫, 又保證數(shù)據(jù)的定期更新, 這種模式基本能夠滿足絕大部分需求。 但是有兩個問題如果同時出現(xiàn),可能就會對應用造成致命的危害:

  • 當前key是一個熱點key并發(fā)量非常大。 (以板藍根為例,疫情之前不是熱點數(shù)據(jù),就沒有放在緩存中,但是聽說可以抗新冠,火了起來,此時這些請求打在數(shù)據(jù)庫上可能把數(shù)據(jù)庫直接搞宕機)
  • 重建緩存不能在短時間完成, 可能是一個復雜計算, 例如復雜的SQL、 多次IO、 多個依賴等。

在緩存失效的瞬間,有大量線程來重建緩存,造成后端負載加大,甚至可能會讓應用崩潰.

解決方案:

解決這個問題主要就是要避免大量線程同時重建緩存。我們可以利用互斥鎖來解決,此方法只允許一個線程重建緩存,其他線程等待重建緩存的線程執(zhí)行完,重新從

緩存獲取數(shù)據(jù)即可。

代碼示例:

String get(String key) {
    // 從Redis中獲取數(shù)據(jù)
    String value = redis.get(key);
    // 如果value為空, 則開始重構緩存
    if (value == null){
        // 只允許一個線程重建緩存, 使用nx, 并設置過期時間ex
        String mutexKey = "mutext:key:" + key;
        if (redis.set(mutexKey, "1", "ex 180", "nx")){
            // 從數(shù)據(jù)源獲取數(shù)據(jù)
            value = db.get(key);
            // 回寫Redis, 并設置過期時間
            redis.setex(key, timeout, value);
            // 刪除key_mutex
            redis.delete(mutexKey);
        }else{
            Thread.sleep(50);
            get(key);
        }
    }
    return value;
}

5. 緩存和數(shù)據(jù)庫雙寫不一致

在大并發(fā)下,同時操作數(shù)據(jù)庫與緩存會存在數(shù)據(jù)不一致性問題

1、雙寫不一致問題

線程1寫完數(shù)據(jù)庫,還未更新緩存,線程2又寫數(shù)據(jù)庫,更新緩存,最好線程1又更新了緩存,此時造成數(shù)據(jù)庫和緩存中的數(shù)據(jù)我不一致的。

2、讀寫并發(fā)不一致

1.線程1 寫進數(shù)據(jù)庫然后刪除緩存

2.線程3查詢緩存是空的,從數(shù)據(jù)庫中拿到數(shù)據(jù)但是還未放入緩存

3.線程2又進來了寫數(shù)據(jù)庫并刪除了緩存

4.線程3繼續(xù)更新緩存 此時緩存和數(shù)據(jù)庫中的數(shù)據(jù)還是不一致的。

解決方案:

1、對于并發(fā)幾率很小的數(shù)據(jù)(如個人維度的訂單數(shù)據(jù)、用戶數(shù)據(jù)等),這種幾乎不用考慮這個問題,很少會發(fā)生緩存不一致,可以給緩存數(shù)據(jù)加上過期時間,每隔一段時間觸發(fā)讀的主動更新即可。

2、就算并發(fā)很高,如果業(yè)務上能容忍短時間的緩存數(shù)據(jù)不一致(如商品名稱,商品分類菜單等),緩存加上過期時間依然可以解決大部分業(yè)務對于緩存的要求

3、如果不能容忍緩存數(shù)據(jù)不一致,可以通過加讀寫鎖保證并發(fā)讀寫或寫寫的時候按順序排好隊,讀讀的時候相 當于無鎖

4、也可以用阿里開源的canal通過監(jiān)聽數(shù)據(jù)庫的binlog日志及時的去修改緩存,但是引入了新的中間件,增加了系統(tǒng)的復雜度。

6.Redis對過期key的三種清除策略

1)被動刪除

當讀/寫一個已經(jīng)過期的key時,會觸發(fā)惰性刪除策略,直接刪除掉這個過期key

2)主動刪除

由于惰性刪除策略無法保證冷數(shù)據(jù)被及時刪掉,所以Redis會定期主動淘汰一批已過期的key

3)內(nèi)存淘汰

主動清理策略在Redis 4.0 之前一共實現(xiàn)了 6 種內(nèi)存淘汰策略,在 4.0 之后,又增加了 2 種策 略,總共8種:1

a)針對設置了過期時間的key做處理:

  • volatile-ttl:在篩選時,會針對設置了過期時間的鍵值對,根據(jù)過期時間的先后進行刪 除,越早過期的越先被刪除。
  • volatile-random:就像它的名稱一樣,在設置了過期時間的鍵值對中,進行隨機刪除。
  • volatile-lru:會使用 LRU 算法篩選設置了過期時間的鍵值對刪除。
  • volatile-lfu:會使用 LFU 算法篩選設置了過期時間的鍵值對刪除。

b) 針對所有的key做處理:

  • allkeys-random:從所有鍵值對中隨機選擇并刪除數(shù)據(jù)。
  • allkeys-lru:使用 LRU 算法在所有數(shù)據(jù)中進行篩選刪除。
  • allkeys-lfu:使用 LFU 算法在所有數(shù)據(jù)中進行篩選刪除。

c) 不處理:

  • noeviction:不會剔除任何數(shù)據(jù),拒絕所有寫入操作并返回客戶端錯誤信息"(error) OOM command not allowed when used memory",此時Redis只響應讀操作。

LRU 算法(Least Recently Used,最近最少使用)

淘汰很久沒被訪問過的數(shù)據(jù),以最近一次訪問時間作為參考。

LFU 算法(Least Frequently Used,最不經(jīng)常使用)

淘汰最近一段時間被訪問次數(shù)最少的數(shù)據(jù),以次數(shù)作為參考。

如何選擇主動清理策略?

當存在熱點數(shù)據(jù)時,LRU的效率很好,但偶發(fā)性的、周期性的批量操作(一個不常用的key被突然訪問了一下,此時訪問時間比一些熱點key要晚,導致熱點key被清理)會導致LRU命中率急劇下降,緩存污染情況比較嚴重。這時使用LFU可能更好點。

根據(jù)自身業(yè)務類型,配置好maxmemory-policy(默認是noeviction),推薦使用volatile-lru。如 果不設置最大內(nèi)存,當 Redis 內(nèi)存超出物理內(nèi)存限制時,內(nèi)存的數(shù)據(jù)會開始和磁盤產(chǎn)生頻繁的交換 (swap),會讓 Redis 的性能急劇下降。當Redis運行在主從模式時,只有主結點才會執(zhí)行過期刪除策略,然后把刪除操作”del ,key”同步到從結點刪除數(shù)據(jù)

總結

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

相關文章

  • Redis中List實現(xiàn)雙鏈表

    Redis中List實現(xiàn)雙鏈表

    本文主要介紹了Redis中List實現(xiàn)雙鏈表,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • Redis鎖完美解決高并發(fā)秒殺問題

    Redis鎖完美解決高并發(fā)秒殺問題

    本文主要介紹了Redis鎖完美解決高并發(fā)秒殺問題,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • redis秒殺系統(tǒng)的實現(xiàn)

    redis秒殺系統(tǒng)的實現(xiàn)

    秒殺在很多活動大促中都可以用到,本文主要介紹了redis秒殺系統(tǒng)的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • redis yml配置的用法小結

    redis yml配置的用法小結

    RedisYML配置是Redis的一種配置文件格式,,對Redis的配置進行統(tǒng)一管理,本文就來介紹了redis yml配置的用法小結,具有一定的參考價值,感興趣的可以了解一下
    2024-02-02
  • Redis基本數(shù)據(jù)類型String常用操作命令

    Redis基本數(shù)據(jù)類型String常用操作命令

    這篇文章主要為大家介紹了Redis基本數(shù)據(jù)類型String常用操作命令,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計算法講解

    Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計算法講解

    這篇文章主要為大家介紹了Redis特殊數(shù)據(jù)類型HyperLogLog基數(shù)統(tǒng)計算法講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • 詳解redis desktop manager安裝及連接方式

    詳解redis desktop manager安裝及連接方式

    這篇文章主要介紹了redis desktop manager安裝及連接方式,本文圖文并茂給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • Redis高性能的原因及說明

    Redis高性能的原因及說明

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

    redis加鎖的幾種方式匯總

    這篇文章主要介紹了redis加鎖的幾種方式匯總,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Redis list 類型學習筆記與總結

    Redis list 類型學習筆記與總結

    這篇文章主要介紹了Redis list 類型學習筆記與總結,本文著重講解了關于List的一些常用方法,比如lpush 方法、lrange 方法、rpush 方法、linsert 方法、 lset 方法等,需要的朋友可以參考下
    2015-06-06

最新評論