Redis中的内存释放与过期键删除

来源:互联网 发布:linux vi如何添加 编辑:程序博客网 时间:2024/06/16 18:14

目录(?)[+]

简介

在Redis中,内存的大小是有限的,所以为了防止内存饱和,需要实现某种键淘汰策略。主要有两种方法,一种是当Redis内存不足时所采用的内存释放策略。另一种是对过期键进行删除的策略,也可以在某种程度上释放内存。

相关数据结构

Redis中的数据库结构如下:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 数据库结构 
  3.  */  
  4. typedef struct redisDb {  
  5.     // key space,包括键值对象  
  6.     dict *dict;                 /* The keyspace for this DB */  
  7.     // 保存 key 的过期时间  
  8.     dict *expires;              /* Timeout of keys with a timeout set */  
  9.     // 正因为某个/某些 key 而被阻塞的客户端  
  10.     dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */  
  11.     // 某个/某些接收到 PUSH 命令的阻塞 key  
  12.     dict *ready_keys;           /* Blocked keys that received a PUSH */  
  13.     // 正在监视某个/某些 key 的所有客户端  
  14.     dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */  
  15.     // 数据库的号码  
  16.     int id;  
  17. } redisDb;  

其中的expires字典保存了数据库总所有键的过期时间。在expires里,对象中的键和dict一样,但是它的value是标识过期时间的值,以便在删除过期键的时候使用。

Redis的服务器中有一个为LRU算法准备的lruclock:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. struct redisServer{  
  2.        ....  
  3.        /* Clock incrementing every minute, for LRU */  
  4.        unsigned lruclock:22;  
  5.        ....  
  6. }  

这个lruclock会在定时调用的函数serverCron中进行实时更新。而创建对象的时候,会将对象的lru设置成当前的服务器的lruclock。同样,在访问键的时候,会对lru进行一次更新。

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 根据给定类型和值,创建新对象 
  3.  */  
  4. robj *createObject(int type, void *ptr) {  
  5.   
  6.     // 分配空间  
  7.     robj *o = zmalloc(sizeof(*o));  
  8.     ......  
  9.     /* Set the LRU to the current lruclock (minutes resolution). */  
  10.     o->lru = server.lruclock;  
  11.   
  12.     return o;  
  13. }  

内存释放的策略

Redis中有专门释放内存的函数:freeMmoryIfNeeded。每当执行一个命令的时候,就会调用该函数来检测内存是否够用。如果已用内存大于最大内存限制,它就会进行内存释放。

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /* Check if we are over the memory limit. */  
  2. if (mem_used <= server.maxmemory) return REDIS_OK;  
  3.   
  4. if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)  
  5.     return REDIS_ERR; /* We need to free memory, but policy forbids. */  

当需要进行内存释放的时候,需要用某种策略对保存的的对象进行删除。Redis有六种策略:

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据

先判断是从过期集expires中删除键还是从所有数据集dict中删除键

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||  
  2.     server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)  
  3. {  
  4.     dict = server.db[j].dict;  
  5. else {  
  6.     dict = server.db[j].expires;  
  7. }  
如果是随机算法,就直接挑选一个随机键进行删除

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /* volatile-random and allkeys-random policy */  
  2. // 随机算法  
  3. if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||  
  4.     server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)  
  5. {  
  6.     de = dictGetRandomKey(dict);  
  7.     bestkey = dictGetKey(de);  
  8. }  

如果是LRU算法,就采用局部的LRU。意思是不是从所有数据中找到LRU,而是随机找到若干个键,删除其中的LRU键。

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /* volatile-lru and allkeys-lru policy */  
  2. // LRU 算法  
  3. else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||  
  4.     server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)  
  5. {  
  6.     for (k = 0; k < server.maxmemory_samples; k++) {  
  7.         sds thiskey;  
  8.         long thisval;  
  9.         robj *o;  
  10.   
  11.         de = dictGetRandomKey(dict);  
  12.         thiskey = dictGetKey(de);  
  13.         /* When policy is volatile-lru we need an additional lookup 
  14.          * to locate the real key, as dict is set to db->expires. */  
  15.         if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)  
  16.             de = dictFind(db->dict, thiskey);  
  17.         o = dictGetVal(de);  
  18.         thisval = estimateObjectIdleTime(o);  
  19.   
  20.         /* Higher idle time is better candidate for deletion */  
  21.         if (bestkey == NULL || thisval > bestval) {  
  22.             bestkey = thiskey;  
  23.             bestval = thisval;  
  24.         }  
  25.     }  
  26. }  
如果是TTL算法,就在expires中随机挑几个数据,找到最近的要过期的键进行删除。

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /* volatile-ttl */  
  2. // TTL 算法  
  3. else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {  
  4.     for (k = 0; k < server.maxmemory_samples; k++) {  
  5.         sds thiskey;  
  6.         long thisval;  
  7.   
  8.         de = dictGetRandomKey(dict);  
  9.         thiskey = dictGetKey(de);  
  10.         thisval = (long) dictGetVal(de);  
  11.   
  12.         /* Expire sooner (minor expire unix timestamp) is better 
  13.          * candidate for deletion */  
  14.         if (bestkey == NULL || thisval < bestval) {  
  15.             bestkey = thiskey;  
  16.             bestval = thisval;  
  17.         }  
  18.     }  
  19. }  

过期键删除的策略

惰性删除

所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查。如果过期就删除,如果没过期就正常访问。这种只有在访问时才检查过期的策略叫做定期删除。

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. int expireIfNeeded(redisDb *db, robj *key) {  
  2.     // 取出 key 的过期时间  
  3.     long long when = getExpire(db,key);  
  4.   
  5.     // key 没有过期时间,直接返回  
  6.     if (when < 0) return 0; /* No expire for this key */  
  7.   
  8.     /* Don't expire anything while loading. It will be done later. */  
  9.     // 不要在服务器载入数据时执行过期  
  10.     if (server.loading) return 0;  
  11.   
  12.     /* If we are running in the context of a slave, return ASAP: 
  13.      * the slave key expiration is controlled by the master that will 
  14.      * send us synthesized DEL operations for expired keys. 
  15.      * 
  16.      * Still we try to return the right information to the caller,  
  17.      * that is, 0 if we think the key should be still valid, 1 if 
  18.      * we think the key is expired at this time. */  
  19.     // 如果服务器作为附属节点运行,那么直接返回  
  20.     // 因为附属节点的过期是由主节点通过发送 DEL 命令来删除的  
  21.     // 不必自主删除  
  22.     if (server.masterhost != NULL) {  
  23.         // 返回一个理论上正确的值,但不执行实际的删除操作  
  24.         return mstime() > when;  
  25.     }  
  26.   
  27.     /* Return when this key has not expired */  
  28.     // 未过期  
  29.     if (mstime() <= when) return 0;  
  30.   
  31.     /* Delete the key */  
  32.     server.stat_expiredkeys++;  
  33.   
  34.     // 传播过期命令  
  35.     propagateExpire(db,key);  
  36.   
  37.     // 从数据库中删除 key  
  38.     return dbDelete(db,key);  
  39. }  

定期删除

每当周期性函数serverCron执行时,会调用activeExpireCycle进行主动的过期键删除。具体方法是在规定的时间内,多次从expires中随机挑一个键,检查它是否过期,如果过期则删除。

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. void activeExpireCycle(void) {  
  2.     int j, iteration = 0;  
  3.     long long start = ustime(), timelimit;  
  4.   
  5.     /* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time 
  6.      * per iteration. Since this function gets called with a frequency of 
  7.      * REDIS_HZ times per second, the following is the max amount of 
  8.      * microseconds we can spend in this function. */  
  9.     // 这个函数可以使用的时长(毫秒)  
  10.     timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100;  
  11.     if (timelimit <= 0) timelimit = 1;  
  12.   
  13.     for (j = 0; j < server.dbnum; j++) {  
  14.         int expired;  
  15.         redisDb *db = server.db+j;  
  16.   
  17.         /* Continue to expire if at the end of the cycle more than 25% 
  18.          * of the keys were expired. */  
  19.         do {  
  20.             unsigned long num = dictSize(db->expires);  
  21.             unsigned long slots = dictSlots(db->expires);  
  22.             long long now = mstime();  
  23.   
  24.             /* When there are less than 1% filled slots getting random 
  25.              * keys is expensive, so stop here waiting for better times... 
  26.              * The dictionary will be resized asap. */  
  27.             // 过期字典里只有 %1 位置被占用,调用随机 key 的消耗比较高  
  28.             // 等 key 多一点再来  
  29.             if (num && slots > DICT_HT_INITIAL_SIZE &&  
  30.                 (num*100/slots < 1)) break;  
  31.   
  32.             /* The main collection cycle. Sample random keys among keys 
  33.              * with an expire set, checking for expired ones. */  
  34.             // 从过期字典中随机取出 key ,检查它是否过期  
  35.             expired = 0;    // 被删除 key 计数  
  36.             if (num > REDIS_EXPIRELOOKUPS_PER_CRON) // 最多每次可查找的次数  
  37.                 num = REDIS_EXPIRELOOKUPS_PER_CRON;  
  38.             while (num--) {  
  39.                 dictEntry *de;  
  40.                 long long t;  
  41.   
  42.                 // 随机查找带有 TTL 的 key ,看它是否过期  
  43.                 // 如果数据库为空,跳出  
  44.                 if ((de = dictGetRandomKey(db->expires)) == NULL) break;  
  45.   
  46.                 t = dictGetSignedIntegerVal(de);  
  47.                 if (now > t) {  
  48.                     // 已过期  
  49.                     sds key = dictGetKey(de);  
  50.                     robj *keyobj = createStringObject(key,sdslen(key));  
  51.   
  52.                     propagateExpire(db,keyobj);  
  53.                     dbDelete(db,keyobj);  
  54.                     decrRefCount(keyobj);  
  55.                     expired++;  
  56.                     server.stat_expiredkeys++;  
  57.                 }  
  58.             }  
  59.             /* We can't block forever here even if there are many keys to 
  60.              * expire. So after a given amount of milliseconds return to the 
  61.              * caller waiting for the other active expire cycle. */  
  62.             // 每次进行 16 次循环之后,检查时间是否超过,如果超过,则退出  
  63.             iteration++;  
  64.             if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */  
  65.                 (ustime()-start) > timelimit) return;  
  66.   
  67.         } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);  
  68.     }  
  69. }  


此文章来自于【http://blog.csdn.net/caishenfans/article/details/44902651】

0 0
原创粉丝点击