redis數(shù)據(jù)淘汰策略介紹

redis數(shù)據(jù)淘汰策略介紹

本文講的是 當(dāng)redis設(shè)定了最大內(nèi)存之后,緩存中的數(shù)據(jù)集大小超過了一定比例,實施的淘汰策略,不是刪除過期鍵的策略,雖然兩者非常相似。

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

設(shè)置最大內(nèi)存大小可以保證redis對外提供穩(wěn)健服務(wù)。

推薦:redis教程

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

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

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

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

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

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

no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)

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

LRU 數(shù)據(jù)淘汰機(jī)制

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

另外,從 Struct redisObject 中可以發(fā)現(xiàn),每一個 redis 對象都會設(shè)置相應(yīng)的 lru??梢韵胂蟮氖?,每一次訪問數(shù)據(jù)的時候,會更新 redisObject.lru。

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

//?redisServer?保存了?lru?計數(shù)器  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?為了節(jié)省空間,提供多種方式來保存一個數(shù)據(jù)  //?譬如:“123456789”?會被存儲為整數(shù)?123456789  unsigned?encoding:4;  unsigned?lru:22;?/*?lru?time?(relative?to?server.lruclock)?*/  ?  //?引用數(shù)  int?refcount;  ?  //?數(shù)據(jù)指針  void?*ptr;  }?robj;  ?  //?redis?定時執(zhí)行程序。聯(lián)想: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();  ......  }  ?  //?更新服務(wù)器的?lru?計數(shù)器  void?updateLRUClock(void)?{  server.lruclock?=?(server.unixtime/REDIS_LRU_CLOCK_RESOLUTION)?&  REDIS_LRU_CLOCK_MAX;  }

TTL 數(shù)據(jù)淘汰機(jī)制

redis 數(shù)據(jù)集數(shù)據(jù)結(jié)構(gòu)中保存了鍵值對過期時間的表,即 redisDb.expires。和 LRU 數(shù)據(jù)淘汰機(jī)制類似,TTL 數(shù)據(jù)淘汰機(jī)制是這樣的:從過期時間的表中隨機(jī)挑選幾個鍵值對,取出其中 ttl 最大的鍵值對淘汰。同樣你會發(fā)現(xiàn),redis 并不是保證取得所有過期時間的表中最快過期的鍵值對,而只是隨機(jī)挑選的幾個鍵值對中的。

總結(jié)

redis 每服務(wù)客戶端執(zhí)行一個命令的時候,會檢測使用的內(nèi)存是否超額。如果超額,即進(jìn)行數(shù)據(jù)淘汰。

//?執(zhí)行命令  int?processCommand(redisClient?*c)?{  ......  //?內(nèi)存超額  /*?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;  }  }  ......  }  ?  //?如果需要,是否一些內(nèi)存  int?freeMemoryIfNeeded(void)?{  size_t?mem_used,?mem_tofree,?mem_freed;  int?slaves?=?listLength(server.slaves);  ?  //?redis?從機(jī)回復(fù)空間和?AOF?內(nèi)存大小不計算入?redis?內(nèi)存大小  /*?Remove?the?size?of?slaves?output?buffers?and?AOF?buffer?from?the  *?count?of?used?memory.?*/  mem_used?=?zmalloc_used_memory();  ?  //?從機(jī)回復(fù)空間大小  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();  }  ?  //?內(nèi)存是否超過設(shè)置大小  /*?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);  ?  //?計算數(shù)據(jù)的空閑時間  thisval?=?estimateObjectIdleTime(o);  ?  //?當(dāng)前鍵值空閑時間更長,則記錄  /*?Higher?idle?time?is?better?candidate?for?deletion?*/  if?(bestkey?==?NULL?||?thisval?>?bestval)?{  bestkey?=?thiskey;  bestval?=?thisval;  }  }  }  ?  //?TTL?策略:挑選將要過期的數(shù)據(jù)  /*?volatile-ttl?*/  else?if?(server.maxmemory_policy?==?REDIS_MAXMEMORY_VOLATILE_TTL)?{  //?server.maxmemory_samples?為隨機(jī)挑選鍵值對次數(shù)  //?隨機(jī)挑選?server.maxmemory_samples個鍵值對,驅(qū)逐最快要過期的數(shù)據(jù)  for?(k?=?0;?k?id);  decrRefCount(keyobj);  keys_freed++;  ?  //?將從機(jī)回復(fù)空間中的數(shù)據(jù)及時發(fā)送給從機(jī)  /*?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?使用的內(nèi)存大小依舊超額,失敗返回  if?(!keys_freed)?return?REDIS_ERR;?/*?nothing?to?free...?*/  }  return?REDIS_OK;  }

適用場景

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

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

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

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

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

相關(guān)推薦:mysql視頻教程:https://www.php.cn/course/list/51.html

以上就是

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊14 分享