Redis中的内存释放与过期键删除
来源:互联网 发布:阿里云弹性公网ip绑定 编辑:程序博客网 时间:2024/06/06 17:42
简介
在Redis中,内存的大小是有限的,所以为了防止内存饱和,需要实现某种键淘汰策略。主要有两种方法,一种是当Redis内存不足时所采用的内存释放策略。另一种是对过期键进行删除的策略,也可以在某种程度上释放内存。
相关数据结构
Redis中的数据库结构如下:
/* * 数据库结构 */typedef struct redisDb { // key space,包括键值对象 dict *dict; /* The keyspace for this DB */ // 保存 key 的过期时间 dict *expires; /* Timeout of keys with a timeout set */ // 正因为某个/某些 key 而被阻塞的客户端 dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */ // 某个/某些接收到 PUSH 命令的阻塞 key dict *ready_keys; /* Blocked keys that received a PUSH */ // 正在监视某个/某些 key 的所有客户端 dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ // 数据库的号码 int id;} redisDb;
其中的expires字典保存了数据库总所有键的过期时间。在expires里,对象中的键和dict一样,但是它的value是标识过期时间的值,以便在删除过期键的时候使用。
Redis的服务器中有一个为LRU算法准备的lruclock:
struct redisServer{ .... /* Clock incrementing every minute, for LRU */ unsigned lruclock:22; ....}
这个lruclock会在定时调用的函数serverCron中进行实时更新。而创建对象的时候,会将对象的lru设置成当前的服务器的lruclock。同样,在访问键的时候,会对lru进行一次更新。
/* * 根据给定类型和值,创建新对象 */robj *createObject(int type, void *ptr) { // 分配空间 robj *o = zmalloc(sizeof(*o)); ...... /* Set the LRU to the current lruclock (minutes resolution). */ o->lru = server.lruclock; return o;}
内存释放的策略
Redis中有专门释放内存的函数:freeMmoryIfNeeded。每当执行一个命令的时候,就会调用该函数来检测内存是否够用。如果已用内存大于最大内存限制,它就会进行内存释放。
/* Check if we are over the memory limit. */ if (mem_used <= server.maxmemory) return REDIS_OK; if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION) return REDIS_ERR; /* We need to free memory, but policy forbids. */
当需要进行内存释放的时候,需要用某种策略对保存的的对象进行删除。Redis有六种策略:
- 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(驱逐):禁止驱逐数据
先判断是从过期集expires中删除键还是从所有数据集dict中删除键
if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) { dict = server.db[j].dict; } else { dict = server.db[j].expires; }如果是随机算法,就直接挑选一个随机键进行删除
/* volatile-random and allkeys-random policy */ // 随机算法 if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM || server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM) { de = dictGetRandomKey(dict); bestkey = dictGetKey(de); }
如果是LRU算法,就采用局部的LRU。意思是不是从所有数据中找到LRU,而是随机找到若干个键,删除其中的LRU键。
/* volatile-lru and allkeys-lru policy */ // LRU 算法 else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) { for (k = 0; k < server.maxmemory_samples; k++) { sds thiskey; long thisval; robj *o; de = dictGetRandomKey(dict); thiskey = dictGetKey(de); /* When policy is volatile-lru we need an additional lookup * to locate the real key, as dict is set to db->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算法,就在expires中随机挑几个数据,找到最近的要过期的键进行删除。
/* volatile-ttl */ // TTL 算法 else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_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命令在执行之前都会调用expireIfNeeded函数对输入键进行检查。如果过期就删除,如果没过期就正常访问。这种只有在访问时才检查过期的策略叫做定期删除。
int expireIfNeeded(redisDb *db, robj *key) { // 取出 key 的过期时间 long long when = getExpire(db,key); // key 没有过期时间,直接返回 if (when < 0) return 0; /* No expire for this key */ /* Don't expire anything while loading. It will be done later. */ // 不要在服务器载入数据时执行过期 if (server.loading) return 0; /* If we are running in the context of a slave, return ASAP: * the slave key expiration is controlled by the master that will * send us synthesized DEL operations for expired keys. * * Still we try to return the right information to the caller, * that is, 0 if we think the key should be still valid, 1 if * we think the key is expired at this time. */ // 如果服务器作为附属节点运行,那么直接返回 // 因为附属节点的过期是由主节点通过发送 DEL 命令来删除的 // 不必自主删除 if (server.masterhost != NULL) { // 返回一个理论上正确的值,但不执行实际的删除操作 return mstime() > when; } /* Return when this key has not expired */ // 未过期 if (mstime() <= when) return 0; /* Delete the key */ server.stat_expiredkeys++; // 传播过期命令 propagateExpire(db,key); // 从数据库中删除 key return dbDelete(db,key);}
定期删除
每当周期性函数serverCron执行时,会调用activeExpireCycle进行主动的过期键删除。具体方法是在规定的时间内,多次从expires中随机挑一个键,检查它是否过期,如果过期则删除。
void activeExpireCycle(void) { int j, iteration = 0; long long start = ustime(), timelimit; /* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time * per iteration. Since this function gets called with a frequency of * REDIS_HZ times per second, the following is the max amount of * microseconds we can spend in this function. */ // 这个函数可以使用的时长(毫秒) timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100; if (timelimit <= 0) timelimit = 1; for (j = 0; j < server.dbnum; j++) { int expired; redisDb *db = server.db+j; /* Continue to expire if at the end of the cycle more than 25% * of the keys were expired. */ do { unsigned long num = dictSize(db->expires); unsigned long slots = dictSlots(db->expires); long long now = mstime(); /* When there are less than 1% filled slots getting random * keys is expensive, so stop here waiting for better times... * The dictionary will be resized asap. */ // 过期字典里只有 %1 位置被占用,调用随机 key 的消耗比较高 // 等 key 多一点再来 if (num && slots > DICT_HT_INITIAL_SIZE && (num*100/slots < 1)) break; /* The main collection cycle. Sample random keys among keys * with an expire set, checking for expired ones. */ // 从过期字典中随机取出 key ,检查它是否过期 expired = 0; // 被删除 key 计数 if (num > REDIS_EXPIRELOOKUPS_PER_CRON) // 最多每次可查找的次数 num = REDIS_EXPIRELOOKUPS_PER_CRON; while (num--) { dictEntry *de; long long t; // 随机查找带有 TTL 的 key ,看它是否过期 // 如果数据库为空,跳出 if ((de = dictGetRandomKey(db->expires)) == NULL) break; t = dictGetSignedIntegerVal(de); if (now > t) { // 已过期 sds key = dictGetKey(de); robj *keyobj = createStringObject(key,sdslen(key)); propagateExpire(db,keyobj); dbDelete(db,keyobj); decrRefCount(keyobj); expired++; server.stat_expiredkeys++; } } /* We can't block forever here even if there are many keys to * expire. So after a given amount of milliseconds return to the * caller waiting for the other active expire cycle. */ // 每次进行 16 次循环之后,检查时间是否超过,如果超过,则退出 iteration++; if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */ (ustime()-start) > timelimit) return; } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); }}
0 1
- Redis中的内存释放与过期键删除
- Redis中的内存释放与过期键删除
- Redis中的内存释放与过期键删除
- redis过期键删除策略
- Redis过期键删除策略
- redis删除数据后内存释放问题
- Redis的过期键删除策略
- redis学习笔记(16)---过期键的设置与删除
- redis如何删除过期数据
- C语言中的内存分配与释放
- redis中关于过期键的删除策略
- redis过期键删除机制的源码分析
- Redis过期键删除策略及源码剖析
- Redis过期键
- redis键过期
- Redis事务与过期时间
- redis过期时间和删除策略
- redis expire key 过期不删除
- C++中__uuidof是干什么用的
- Spring国际化实现
- 前序创建和遍历二叉树
- http和https协议栈的对比
- Linux学习笔记(四)之用户登录
- Redis中的内存释放与过期键删除
- jQuery模拟Select下拉框
- 背包问题九讲
- Android读写assets目录下面的资源文件(文件夹)
- CLR Scalar-valued函数
- 验证码
- bash中单引号与双引号的区别
- Halcon学习 Matching
- static_cast, dynamic_cast, const_cast, reinterpret_cast