溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

內(nèi)存管理

發(fā)布時(shí)間:2020-07-27 01:38:04 來(lái)源:網(wǎng)絡(luò) 閱讀:375 作者:hxx_BL 欄目:建站服務(wù)器

8.2.內(nèi)存管理

        Redis主要是通過控制內(nèi)存上限和相應(yīng)的回收策略實(shí)現(xiàn)內(nèi)存管理,本節(jié)將圍繞這兩個(gè)方面來(lái)介紹Redis如何管理內(nèi)存。

8.2.1 內(nèi)存上限

        Redis使用maxmemory參數(shù)限制最大可用內(nèi)存。限制內(nèi)存的目的主要有:

  • 用于緩存場(chǎng)景,當(dāng)超出內(nèi)存上限maxmemory時(shí)使用LRU等刪除策略釋放空間。

  • 防止所用內(nèi)存超過服務(wù)器物理內(nèi)存。

        需要注意maxmemory限制的是Redis內(nèi)存實(shí)際使用的內(nèi)存量,也就是used_memory統(tǒng)計(jì)項(xiàng)對(duì)應(yīng)的內(nèi)存。由于內(nèi)存碎片率的存在,實(shí)際消耗的內(nèi)存可能會(huì)比maxmemory設(shè)置的更大,實(shí)際使用時(shí)要小心這部分內(nèi)存溢出。通過內(nèi)存上限可以非常方便的實(shí)現(xiàn)一臺(tái)服務(wù)器部署多個(gè)Redis進(jìn)程的內(nèi)存控制。比如一臺(tái)24GB內(nèi)存的服務(wù)器,為系統(tǒng)預(yù)留4GB內(nèi)存,預(yù)留4GB空閑內(nèi)存給其他進(jìn)程或Redis fork進(jìn)程,留給Redis 16GB內(nèi)存,這樣可以部署4個(gè)maxmemory=4GBRedis進(jìn)程。得益于Redis單線程架構(gòu)和內(nèi)存限制機(jī)制,即使沒有采用虛擬化不同的Redis進(jìn)程之間也可以很好的實(shí)現(xiàn)CPU和內(nèi)存的隔離性,如圖8-2所示。

內(nèi)存管理

8-2:服務(wù)器分配4個(gè)4GBRedis進(jìn)程

8.2.2 動(dòng)態(tài)調(diào)整內(nèi)存上限

        Redis的內(nèi)存上限可以通過config set maxmemory {bytes} 動(dòng)態(tài)修改最大可用內(nèi)存。例如之前的示例,當(dāng)發(fā)現(xiàn)Redis-1沒有做好內(nèi)存預(yù)估實(shí)際只用了不到2GB內(nèi)存,而Redis-2進(jìn)程需要擴(kuò)容到6GB內(nèi)存才夠用,這時(shí)可以分別執(zhí)行如下命令調(diào)整:

Redis-1>config set maxmemory 2GB
Redis-2>config set maxmemory 6GB

        通過動(dòng)態(tài)修改maxmemory,可以實(shí)現(xiàn)在當(dāng)前服務(wù)器下動(dòng)態(tài)伸縮Redis內(nèi)存的目的,如圖8-3所示。

內(nèi)存管理

8-3:redis進(jìn)程之間調(diào)整maxmemory伸縮內(nèi)存

        這個(gè)例子過于理想化,如果此時(shí)Redis-3Redis-4進(jìn)程也需要分別擴(kuò)容到6Gb,這時(shí)超出系統(tǒng)物理內(nèi)存限制就不能簡(jiǎn)單的通過調(diào)整maxmemory來(lái)達(dá)到擴(kuò)容的目的,需要采用在線遷移數(shù)據(jù)或者基于復(fù)制切換服務(wù)器來(lái)達(dá)到擴(kuò)容的目的,具體細(xì)節(jié)見集群章節(jié)和哨兵章節(jié)。

運(yùn)維提示:
         1:Redis默認(rèn)無(wú)限使用服務(wù)器內(nèi)存,為防止極端情況系統(tǒng)內(nèi)存耗盡,建議所有的Redis進(jìn)程都要配置maxmemory。
         2:在保證物理內(nèi)存足夠的情況下,服務(wù)器上所有的redis進(jìn)程可以調(diào)整maxmemory參數(shù)來(lái)達(dá)到自由伸縮最大可用內(nèi)存的目的。

8.2.3 內(nèi)存回收策略

Redis的內(nèi)存回收機(jī)制主要體現(xiàn)在以下兩個(gè)方面:

  • 刪除到達(dá)過期時(shí)間的鍵對(duì)象

  • 內(nèi)存使用達(dá)到maxmemory上線時(shí)觸發(fā)內(nèi)存溢出控制策略

1:刪除過期鍵對(duì)象

   Redis所有的鍵都可以設(shè)置過期屬性,內(nèi)部保存在過期字典中。由于進(jìn)程內(nèi)保存大量的鍵,維護(hù)每個(gè)鍵精準(zhǔn)的過期刪除機(jī)制會(huì)導(dǎo)致消耗大量的CPU,對(duì)于單線程的Redis來(lái)說(shuō)成本過高,因此Redis采用惰性刪除和定時(shí)任務(wù)刪除機(jī)制實(shí)現(xiàn)過期健的內(nèi)存回收。

1)惰性刪除:

  惰性刪除用于當(dāng)客戶端讀取帶有超時(shí)屬性的鍵時(shí),如果已經(jīng)超過鍵設(shè)置的過期時(shí)間,會(huì)執(zhí)行刪除操作并返回空,這種策略是出于節(jié)省CPU成本考慮,不需要單獨(dú)維護(hù)TTL鏈表來(lái)處理過期鍵的刪除。但是這種方式存在內(nèi)存泄露的問題,當(dāng)過期鍵一直沒有訪問將無(wú)法得到及時(shí)刪除,從而導(dǎo)致內(nèi)存不能及時(shí)釋放。正因?yàn)槿绱耍?/span>Redis還提供另一種定時(shí)任務(wù)刪除機(jī)制作為惰性刪除的補(bǔ)充。

2)定時(shí)任務(wù)刪除:

Redis內(nèi)部維護(hù)一個(gè)的定時(shí)任務(wù),默認(rèn)每秒運(yùn)行10(通過參數(shù)hz控制)。定時(shí)任務(wù)中刪除過期鍵邏輯采用了自適應(yīng)算法,根據(jù)鍵的過期比例使用快慢兩種速率模式回收鍵,流程如圖8-4:

內(nèi)存管理

8-4:定時(shí)任務(wù)刪除過期鍵邏輯

流程說(shuō)明:

  • 定時(shí)任務(wù)在每個(gè)數(shù)據(jù)庫(kù)空間隨機(jī)檢查20個(gè)過期鍵,當(dāng)發(fā)現(xiàn)過期時(shí)刪除對(duì)應(yīng)的鍵。

  • 如果超過檢查數(shù)25%的鍵過期,循環(huán)執(zhí)行回收邏輯直到不足25%或運(yùn)行超時(shí)為止,慢模式下超時(shí)時(shí)間為25毫秒。

  • 如果之前回收鍵邏輯運(yùn)行超過,則在Redis觸發(fā)內(nèi)部事件之前再次以快模式運(yùn)行回收過期鍵任務(wù),快模式下超時(shí)時(shí)間為1毫秒且2秒內(nèi)只能運(yùn)行1次。

  • 快慢兩種模式內(nèi)部刪除邏輯相同,只是執(zhí)行的超時(shí)時(shí)間不同。

Icon

開發(fā)提示:

  1. Redis定時(shí)任務(wù)通過hz參數(shù)控制周期,定時(shí)任務(wù)內(nèi)部涉及很多邏輯如:關(guān)閉超時(shí)連接,刪除過期鍵,AOF寫文件頻率,集群定時(shí)通信等,修改這個(gè)參數(shù)影響范圍非常廣,不建議調(diào)大hz參數(shù)來(lái)加速回收內(nèi)存頻率。

  2. 避免大量鍵設(shè)置相同的過期時(shí)間,否則容易產(chǎn)生同一時(shí)刻超過25%的鍵過期場(chǎng)景,觸發(fā)循環(huán)刪除過期鍵邏輯從而拖慢Redis響應(yīng)速度。

2:內(nèi)存溢出控制策略

     當(dāng)Redis所用內(nèi)存達(dá)到maxmemory上限時(shí)會(huì)觸發(fā)相應(yīng)的溢出控制策略。具體策略受maxmemory-policy參數(shù)控制,Redis支持6種策略如下:

  • noeviction:默認(rèn)策略,不會(huì)刪除任何數(shù)據(jù),拒絕所有寫入操作并返回客戶端錯(cuò)誤信息"(error) OOM command not allowed when used memory",此時(shí)Redis只響應(yīng)讀操作。

  • volatile-lru:根據(jù)LRU算法刪除設(shè)置了超時(shí)屬性(expire)的鍵,直到騰出足夠空間為止。如果沒有可刪除的鍵對(duì)象,回退到noeviction策略。

  • allkeys-lru:根據(jù)LRU算法刪除鍵,不管數(shù)據(jù)有沒有設(shè)置超時(shí)屬性,直到騰出足夠空間為止。

  • allkeys-random:隨機(jī)刪除所有鍵,直到騰出足夠空間為止。

  • volatile-random:隨機(jī)刪除過期鍵,直到騰出足夠空間為止。

  • volatile-ttl:根據(jù)鍵值對(duì)象的ttl屬性,刪除最近將要過期數(shù)據(jù)。如果沒有,回退到noeviction策略。

        內(nèi)存溢出控制策略可以采用config set maxmemory-policy {policy} 動(dòng)態(tài)配置。Redis支持豐富的內(nèi)存溢出應(yīng)對(duì)策略,可以根據(jù)實(shí)際需求靈活定制,比如當(dāng)設(shè)置volatile-lru策略時(shí),保證具有過期屬性的鍵可以根據(jù)LRU剔除,而未設(shè)置超時(shí)的鍵可以永久保留。還可以采用allkeys-lru策略把Redis變?yōu)榧兙彺娣?wù)器使用。當(dāng)Redis因?yàn)閮?nèi)存溢出刪除鍵時(shí),可以通過執(zhí)行info stats命令查看evicted_keys指標(biāo)找出當(dāng)前Redis服務(wù)器已剔除的鍵數(shù)量。

   每次Redis執(zhí)行命令時(shí)如果設(shè)置了maxmemory參數(shù),都會(huì)嘗試執(zhí)行回收內(nèi)存操作。當(dāng)Redis一直工作在內(nèi)存溢出(used_memory>maxmemory)的狀態(tài)下且設(shè)置非noeviction策略時(shí),會(huì)頻繁的觸發(fā)回收內(nèi)存的操作,影響Redis服務(wù)器的性能?;厥諆?nèi)存邏輯偽代碼如下:

def freeMemoryIfNeeded() :
    int mem_used, mem_tofree, mem_freed;
    // 計(jì)算當(dāng)前內(nèi)存總量,排除從節(jié)點(diǎn)輸出緩沖區(qū)和AOF緩沖區(qū)的內(nèi)存占用
    int slaves = server.slaves;
    mem_used = used_memory()-slave_output_buffer_size(slaves)-aof_rewrite_buffer_size();
    // 如果當(dāng)前使用小于等于maxmemory退出
    if (mem_used <= server.maxmemory) :
        return REDIS_OK;
    // 如果設(shè)置內(nèi)存溢出策略為noeviction(不淘汰),返回錯(cuò)誤。
    if (server.maxmemory_policy == 'noeviction') :
        return REDIS_ERR;
    // 計(jì)算需要釋放多少內(nèi)存
    mem_tofree = mem_used - server.maxmemory;
    // 初始化已釋放內(nèi)存量
    mem_freed = 0;
    // 根據(jù)maxmemory-policy策略循環(huán)刪除鍵釋放內(nèi)存
    while (mem_freed < mem_tofree) :
        // 迭代Redis所有數(shù)據(jù)庫(kù)空間
        for (int j = 0; j < server.dbnum; j++) :
            String bestkey = null;
            dict dict;
            if (server.maxmemory_policy == 'allkeys-lru' ||
                server.maxmemory_policy == 'allkeys-random'):
                //如果策略是 allkeys-lru/allkeys-random 回收內(nèi)存目標(biāo)為所有的數(shù)據(jù)庫(kù)鍵
                dict = server.db[j].dict;
            else :
                // 如果策略是volatile-lru/volatile-random/volatile-ttl回收內(nèi)存目標(biāo)為帶過期時(shí)間的數(shù)據(jù)庫(kù)鍵
                dict = server.db[j].expires;
            
            // 如果使用的是隨機(jī)策略,那么從目標(biāo)字典中隨機(jī)選出鍵
            if (server.maxmemory_policy == 'allkeys-random' ||
                server.maxmemory_policy == 'volatile-random') :
                //隨機(jī)返回被刪除鍵
                bestkey = get_random_key(dict);
            else if (server.maxmemory_policy == 'allkeys-lru' ||
                server.maxmemory_policy == 'volatile-lru') :
                //循環(huán)隨機(jī)采樣maxmemory_samples次(默認(rèn)5次),返回相對(duì)空閑時(shí)間最長(zhǎng)的鍵
                bestkey = get_lru_key(dict);
            else if (server.maxmemory_policy == 'volatile-ttl') :
                //循環(huán)隨機(jī)采樣maxmemory_samples次,返回最近將要過期的鍵
                bestkey = get_ttl_key(dict);
       
            // 刪除被選中的鍵
            if (bestkey != null) :
                long delta = used_memory();
                deleteKey(bestkey);
                                   // 計(jì)算刪除鍵所釋放的內(nèi)存量
                delta -= used_memory();
                mem_freed += delta;
                //刪除操作同步給從節(jié)點(diǎn)
                if (slaves):
                    flushSlavesOutputBuffers();
            
    return REDIS_OK;

   從偽代碼可以看到,頻繁執(zhí)行回收內(nèi)存成本很高,主要包括查找可回收鍵和刪除鍵的開銷,如果當(dāng)前Redis有從節(jié)點(diǎn),回收內(nèi)存操作對(duì)應(yīng)的刪除命令會(huì)同步到從節(jié)點(diǎn),導(dǎo)致寫放大的問題,如圖8-5所示。

內(nèi)存管理

8-5:回收內(nèi)存觸發(fā)刪除邏輯

Icon

開發(fā)提示:建議線上Redis內(nèi)存工作在maxmemory>used_memory 狀態(tài)下,避免頻繁內(nèi)存回收開銷。

       對(duì)于需要收縮Redis內(nèi)存的場(chǎng)景,可以通過調(diào)小maxmemory來(lái)實(shí)現(xiàn)快速回收。比如對(duì)一個(gè)實(shí)際占用6GB內(nèi)存的進(jìn)程設(shè)置maxmemory=4GB,之后第一次執(zhí)行命令時(shí),如果使用非noeviction策略,它會(huì)一次性回收到maxmemory指定的內(nèi)存量,從而達(dá)到快速回收內(nèi)存的目的,注意此操作會(huì)導(dǎo)致數(shù)據(jù)丟失,一般在緩存場(chǎng)景下使用。

 


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI