redis數據淘汰策略詳解

redis數據淘汰策略詳解

本文講的是 當redis設定了最大內存之后,緩存中的數據集大小超過了一定比例,實施的淘汰策略,不是刪除過期鍵的策略,雖然兩者非常相似。(推薦:redis視頻教程

redis 中,允許用戶設置最大使用內存大小通過配置redis.conf中的maxmemory這個值來開啟內存淘汰功能,在內存限定的情況下是很有用的。

設置最大內存大小可以保證redis對外提供穩健服務。

redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。redis 提供 6種數據淘汰策略通過maxmemory-policy設置策略:

volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰

volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

no-enviction(驅逐):禁止驅逐數據

redis 確定驅逐某個鍵值對后,會刪除這個數據并將這個數據變更消息發布到本地(AOF 持久化)和從機(主從連接)

LRU 數據淘汰機制

在服務器配置中保存了 lru 計數器 server.lrulock,會定時(redis 定時程序 serverCorn())更新,server.lrulock 的值是根據 server.unixtime 計算出來的。

另外,從 Struct redisObject 中可以發現,每一個 redis 對象都會設置相應的 lru。可以想象的是,每一次訪問數據的時候,會更新 redisObject.lru。

LRU 數據淘汰機制是這樣的:在數據集中隨機挑選幾個鍵值對,取出其中 lru 最大的鍵值對淘汰。所以,你會發現,redis 并不是保證取得所有數據集中最近最少使用(LRU)的鍵值對,而只是隨機挑選的幾個鍵值對中的。

//?redisServer?保存了?lru?計數器 struct?redisServer?{ ... unsigned?lruclock:22;?/*?Clock?incrementing?every?minute,?for?LRU?*/ ... }; //?每一個?redis?對象都保存了?lru #define?REDIS_LRU_CLOCK_MAX?((1lru?*/ #define?REDIS_LRU_CLOCK_RESOLUTION?10?/*?LRU?clock?resolution?in?seconds?*/ typedef?struct?redisObject?{ //?剛剛好?32?bits //?對象的類型,字符串/列表/集合/哈希表 unsigned?type:4; //?未使用的兩個位 unsigned?notused:2;?/*?Not?used?*/ //?編碼的方式,redis?為了節省空間,提供多種方式來保存一個數據 //?譬如:“123456789”?會被存儲為整數?123456789 unsigned?encoding:4; unsigned?lru:22;?/*?lru?time?(relative?to?server.lruclock)?*/ //?引用數 int?refcount; //?數據指針 void?*ptr;  }?robj; //?redis?定時執行程序。聯想:linux?cron int?serverCron(struct?aeEventLoop?*eventLoop,?long?long?id,?void?*clientData)?{ ...... /*?We?have?just?22?bits?per?object?for?LRU?information. *?So?we?use?an?(eventually?wrapping)?LRU?clock?with?10?seconds?resolution. *?2^22?bits?with?10?seconds?resolution?is?more?or?less?1.5?years. * *?Note?that?even?if?this?will?wrap?after?1.5?years?it's?not?a?problem, *?everything?will?still?work?but?just?some?object?will?appear?younger *?to?Redis.?But?for?this?to?happen?a?given?object?should?never?be?touched *?for?1.5?years. * *?Note?that?you?can?change?the?resolution?altering?the *?REDIS_LRU_CLOCK_RESOLUTION?define.  */ updateLRUClock(); ...... } //?更新服務器的?lru?計數器 void?updateLRUClock(void)?{ server.lruclock?=?(server.unixtime/REDIS_LRU_CLOCK_RESOLUTION)?& REDIS_LRU_CLOCK_MAX; }

TTL 數據淘汰機制

redis 數據集數據結構中保存了鍵值對過期時間的表,即 redisDb.expires。和 LRU 數據淘汰機制類似,TTL 數據淘汰機制是這樣的:從過期時間的表中隨機挑選幾個鍵值對,取出其中 ttl 最大的鍵值對淘汰。

同樣你會發現,redis 并不是保證取得所有過期時間的表中最快過期的鍵值對,而只是隨機挑選的幾個鍵值對中的。

總結

redis 每服務客戶端執行一個命令的時候,會檢測使用的內存是否超額。如果超額,即進行數據淘汰。

//?執行命令 int?processCommand(redisClient?*c)?{ ...... //?內存超額 /*?Handle?the?maxmemory?directive. * *?First?we?try?to?free?some?memory?if?possible?(if?there?are?volatile *?keys?in?the?dataset).?If?there?are?not?the?only?thing?we?can?do *?is?returning?an?error.?*/ if?(server.maxmemory)?{ int?retval?=?freeMemoryIfNeeded(); if?((c->cmd->flags?&?REDIS_CMD_DENYOOM)?&&?retval?==?REDIS_ERR)?{ flagTransaction(c); addReply(c,?shared.oomerr); return?REDIS_OK; } } ...... } //?如果需要,是否一些內存 int?freeMemoryIfNeeded(void)?{ size_t?mem_used,?mem_tofree,?mem_freed; int?slaves?=?listLength(server.slaves); //?redis?從機回復空間和?AOF?內存大小不計算入?redis?內存大小 /*?Remove?the?size?of?slaves?output?buffers?and?AOF?buffer?from?the *?count?of?used?memory.?*/ mem_used?=?zmalloc_used_memory(); //?從機回復空間大小 if?(slaves)?{ listIter?li; listNode?*ln; listRewind(server.slaves,&li); while((ln?=?listNext(&li)))?{ redisClient?*slave?=?listNodeValue(ln); unsigned?long?obuf_bytes?=?getClientOutputBufferMemoryUsage(slave); if?(obuf_bytes?>?mem_used) mem_used?=?0; else mem_used?-=?obuf_bytes; } } //?server.aof_buf?&&?server.aof_rewrite_buf_blocks if?(server.aof_state?!=?REDIS_AOF_OFF)?{ mem_used?-=?sdslen(server.aof_buf); mem_used?-=?aofRewriteBufferSize(); } //?內存是否超過設置大小 /*?Check?if?we?are?over?the?memory?limit.?*/ if?(mem_used?expires.?*/ if?(server.maxmemory_policy?==?REDIS_MAXMEMORY_VOLATILE_LRU) de?=?dictFind(db->dict,?thiskey); o?=?dictGetVal(de);? //?計算數據的空閑時間 thisval?=?estimateObjectIdleTime(o); //?當前鍵值空閑時間更長,則記錄 /*?Higher?idle?time?is?better?candidate?for?deletion?*/ if?(bestkey?==?NULL?||?thisval?>?bestval)?{ bestkey?=?thiskey; bestval?=?thisval; } } }? //?TTL?策略:挑選將要過期的數據 /*?volatile-ttl?*/ else?if?(server.maxmemory_policy?==?REDIS_MAXMEMORY_VOLATILE_TTL)?{ //?server.maxmemory_samples?為隨機挑選鍵值對次數 //?隨機挑選?server.maxmemory_samples個鍵值對,驅逐最快要過期的數據 for?(k?=?0;?k?id); decrRefCount(keyobj); keys_freed++;? //?將從機回復空間中的數據及時發送給從機 /*?When?the?memory?to?free?starts?to?be?big?enough,?we?may *?start?spending?so?much?time?here?that?is?impossible?to *?deliver?data?to?the?slaves?fast?enough,?so?we?force?the *?transmission?here?inside?the?loop.?*/ if?(slaves)?flushSlavesOutputBuffers(); } }? //?未能釋放空間,且此時?redis?使用的內存大小依舊超額,失敗返回 if?(!keys_freed)?return?REDIS_ERR;?/*?nothing?to?free...?*/ } return?REDIS_OK; }

適用場景

下面看看幾種策略的適用場景:

1、allkeys-lru: 如果我們的應用對緩存的訪問符合冪律分布(也就是存在相對熱點數據),或者我們不太清楚我們應用的緩存訪問分布狀況,我們可以選擇allkeys-lru策略。

2、allkeys-random: 如果我們的應用對于緩存key的訪問概率相等,則可以使用這個策略。

3、volatile-ttl: 這種策略使得我們可以向Redis提示哪些key更適合被eviction。

另外,volatile-lru策略和volatile-random策略適合我們將一個Redis實例既應用于緩存和又應用于持久化存儲的時候,然而我們也可以通過使用兩個Redis實例來達到相同的效果,值得一提的是將key設置過期時間實際上會消耗更多的內存,因此我們建議使用allkeys-lru策略從而更有效率的使用內存。

更多redis知識請關注redis視頻教程欄目。

以上就是

? 版權聲明
THE END
喜歡就支持一下吧
點贊10 分享