Redis過期--淘汰機制的解析和內存占用過高的解決方案


echo編輯整理,歡迎轉載,轉載請聲明文章來源。歡迎添加echo微信(微信號:t2421499075)交流學習。 百戰不敗,依不自稱常勝,百敗不頹,依能奮力前行。——這才是真正的堪稱強大!!!


Redis在我們平時的開發或者練習的時候,往往很容易忽略一個問題,那就是我們的Redis內存占滿的問題。但是在真是的商業開發中,Redis的實際占滿是真正會存在這樣的問題的。那么如果Redis在某一刻占滿內存,我們又沒有對它進行相應的設置它會出現什么情況呢?會不會導致我們整個因為使用Redis而整個業務垮掉?這就是我們本篇文章所要講述的問題。

什么是Redis淘汰機制

Redis內存淘汰機制其實簡單講就是將過期的數據或者很久沒有訪問,或者在一段時間內很少有訪問的數據進行刪除。它分為很多中,有主動的數據淘汰,如:用戶設定過期時間。有被動的淘汰,比如:Redis數據占滿了內存,這個時候就會將過期的數據或者很久沒有訪問的數據刪除掉。

Redis的淘汰有哪些類型

  • 定時過期:每個設置過期時間的key都需要創建一個定時器,到過期時間就會立即清除。該策略可以立即清除過期的數據,對內存很友好;但是會占用大量的CPU資源去處理過期的數據,從而影響緩存的響應時間和吞吐量。
  • 惰性過期:只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。該策略可以最大化地節省CPU資源,卻對內存非常不友好。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,占用大量內存。
  • 定期過期:每隔一定的時間,會掃描一定數量的數據庫的expires字典中一定數量的key,並清除其中已過期的key。該策略是前兩者的一個折中方案。通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和內存資源達到最優的平衡效果。
    (expires字典會保存所有設置了過期時間的key的過期時間數據,其中,key是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis集群中保存的所有鍵。)

定時過期的問題:緩存雪崩

很多人可能對這個詞很熟悉,因為有很多的商業案例展示了血淋淋的教訓,但是也有一部分人估計沒有接觸過。緩存雪崩其實也不是什么新詞,它主要的引起原因就是指緩存中數據大批量的到過期時間。定時過期它本身就有一個缺點,那就是會占用大量的CPU資源,如果我們主動設置過期時間的鍵過多,在同一時間過期,很有可能就會造就我們Redis掛掉。但是這並不是最可怕的,雪崩不僅僅影響自己,還在我們的業務中影響數據庫。因為我們很多業務設計都是在我們Redis的數據過期之后,從新查詢數據庫,但我們Redis主動批量過期的時候,會有大量的請求發送到我們的數據庫,很有可能導致我們的數據庫也掛掉。這才是最大的問題所在。

解決方案:

  • 緩存數據的過期時間設置隨機,防止同一時間大量數據過期現象發生。
  • 如果緩存數據庫是分布式部署,將熱點數據均勻分布在不同搞得緩存數據庫中。
  • 設置熱點數據永遠不過期。

從幾種淘汰策略中其實我們可以看到基本的一些問題所在,所以我們在使用緩存的時候最好有一個全面的了解和全面的考慮應對。在實際開發中,我們更應該多去關注的和了解的是定期過期,因為它涉及真實開發中的一些問題。所以我們應該提前設置好。

怎么設置定期過期最大使用內存

定期過期的最大內存設置在我們的redis.conf文件中,我們可以在該文件中看到這個配置:maxmemory <bytes>,但是這個配置一般都是注釋掉的,也就是說安裝之后如果我們沒有主動對他進行配置,那么他就不會有默認大小值。對應的它不設置的情況下,那么它可以使用多少的內存空間呢?這個跟系統有關。如果說我們將Redis安裝在32位的系統上,它的最大使用內存空間應該是在3G左右,如果是64位的系統,那么可以將我們的內存占滿。當然如果真正占滿內存,這是一件比較惡劣的事情,不僅僅訪問Redis的時候,我們不能在進行寫的操作,而且我們系統本身的其他操作也會受到限制。所以我們可以采用命令來對它進行一個初始化的設置

config set maxmemory 268435456

使用命令進行設置之后我們需要重啟Redis才能生效。當然我們也可以直接找到Redis的安裝目錄,然后使用vi命令,直接更改配置文件中的對應的該內容,更改完之后,重啟即可。

定期過期的淘汰策略

  • volatile-lru:根據LRU算法生成的過期時間來刪除。
  • allkeys-lru:根據LRU算法刪除任何key。
  • volatile-lfu:從所有配置了過期時間的鍵中驅逐使用頻率最少的鍵
  • allkeys-lfu:從所有鍵中驅逐使用頻率最少的鍵
  • volatile-random:根據過期設置來隨機刪除key。
  • allkeys-random:無差別隨機刪。
  • volatile-ttl:根據最近過期時間來刪除(輔以TTL)
  • noeviction:誰也不刪,直接在寫操作時返回錯誤。

隨機淘汰策略

隨機找hash桶再次hash指定位置的dictEntry即可。就是在場景REDIS_MAXMEMORY_VOLATILE_RANDOM和REDIS_MAXMEMORY_ALLKEYS_LRU情況下的待淘汰的key。我們可以一觀它的源碼:

dictEntry *dictGetRandomKey(dict *d)
{
    dictEntry *he, *orighe;
    unsigned int h;
    int listlen, listele;
 
    if (dictSize(d) == 0) return NULL;
 
    if (dictIsRehashing(d)) _dictRehashStep(d);
 
    if (dictIsRehashing(d)) {
        // T = O(N)
        do {
            h = random() % (d->ht[0].size+d->ht[1].size);
            he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
                                      d->ht[0].table[h];
        } while(he == NULL);
    } else {
        // T = O(N)
        do {
            h = random() & d->ht[0].sizemask;
            he = d->ht[0].table[h];
        } while(he == NULL);
    }
 
    /* Now we found a non empty bucket, but it is a linked
     * list and we need to get a random element from the list.
     * The only sane way to do so is counting the elements and
     * select a random index. */
    listlen = 0;
    orighe = he;
    while(he) {
        he = he->next;
        listlen++;
    }
    listele = random() % listlen;
    he = orighe;
    // T = O(1)
    while(listele--) he = he->next;
 
    return he;
}

TTL時間淘汰

for (k = 0; k < server.maxmemory_samples; k++) {
    sds thiskey;
    long thisval;

    de = dictGetRandomKey(dict);
    thiskey = dictGetKey(de);
    thisval = (long) dictGetVal(de);

    /* Expire sooner (minor expire unix timestamp) is better
     * candidate for deletion */
    if (bestkey == NULL || thisval < bestval) {
        bestkey = thiskey;
        bestval = thisval;
    }
}

更多源碼,請參考redis官網。這里只是簡單的展示兩種。我們可以根據這樣的代碼來對我們的redis的定期過期做一個合理的配置

思考:

基於一個數據結構做緩存,怎么實現一個LRU算法?

做一個有底線的博客主


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM