简介
在Redis中,内存的大小是有限的,所以为了防止内存饱和,需要实现某种键淘汰策略。主要有两种方法,一种是当Redis内存不足时所采用的内存释放策略。另一种是对过期键进行删除的策略,也可以在某种程度上释放内存。
相关数据结构
Redis中的数据库结构如下:
-
-
-
- typedef struct redisDb {
-
- dict *dict;
-
- dict *expires;
-
- dict *blocking_keys;
-
- dict *ready_keys;
-
- dict *watched_keys;
-
- int id;
- } redisDb;
其中的expires字典保存了数据库总所有键的过期时间。在expires里,对象中的键和dict一样,但是它的value是标识过期时间的值,以便在删除过期键的时候使用。
Redis的服务器中有一个为LRU算法准备的lruclock:
- struct redisServer{
- ....
-
- unsigned lruclock:22;
- ....
- }
这个lruclock会在定时调用的函数serverCron中进行实时更新。而创建对象的时候,会将对象的lru设置成当前的服务器的lruclock。同样,在访问键的时候,会对lru进行一次更新。
-
-
-
- robj *createObject(int type, void *ptr) {
-
-
- robj *o = zmalloc(sizeof(*o));
- ......
-
- o->lru = server.lruclock;
-
- return o;
- }
内存释放的策略
Redis中有专门释放内存的函数:freeMmoryIfNeeded。每当执行一个命令的时候,就会调用该函数来检测内存是否够用。如果已用内存大于最大内存限制,它就会进行内存释放。
-
- if (mem_used <= server.maxmemory) return REDIS_OK;
-
- if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)
- return REDIS_ERR;
当需要进行内存释放的时候,需要用某种策略对保存的的对象进行删除。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;
- }
如果是随机算法,就直接挑选一个随机键进行删除
-
-
- 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键。
-
-
- 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);
-
-
- if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
- de = dictFind(db->dict, thiskey);
- o = dictGetVal(de);
- thisval = estimateObjectIdleTime(o);
-
-
- if (bestkey == NULL || thisval > bestval) {
- bestkey = thiskey;
- bestval = thisval;
- }
- }
- }
如果是TTL算法,就在expires中随机挑几个数据,找到最近的要过期的键进行删除。
-
-
- 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);
-
-
-
- if (bestkey == NULL || thisval < bestval) {
- bestkey = thiskey;
- bestval = thisval;
- }
- }
- }
过期键删除的策略
惰性删除
所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查。如果过期就删除,如果没过期就正常访问。这种只有在访问时才检查过期的策略叫做定期删除。
- int expireIfNeeded(redisDb *db, robj *key) {
-
- long long when = getExpire(db,key);
-
-
- if (when < 0) return 0;
-
-
-
- if (server.loading) return 0;
-
-
-
-
-
-
-
-
-
-
-
- if (server.masterhost != NULL) {
-
- return mstime() > when;
- }
-
-
-
- if (mstime() <= when) return 0;
-
-
- server.stat_expiredkeys++;
-
-
- propagateExpire(db,key);
-
-
- return dbDelete(db,key);
- }
定期删除
每当周期性函数serverCron执行时,会调用activeExpireCycle进行主动的过期键删除。具体方法是在规定的时间内,多次从expires中随机挑一个键,检查它是否过期,如果过期则删除。
- void activeExpireCycle(void) {
- int j, iteration = 0;
- long long start = ustime(), timelimit;
-
-
-
-
-
-
- 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;
-
-
-
- do {
- unsigned long num = dictSize(db->expires);
- unsigned long slots = dictSlots(db->expires);
- long long now = mstime();
-
-
-
-
-
-
- if (num && slots > DICT_HT_INITIAL_SIZE &&
- (num*100/slots < 1)) break;
-
-
-
-
- expired = 0;
- if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
- num = REDIS_EXPIRELOOKUPS_PER_CRON;
- while (num--) {
- dictEntry *de;
- long long t;
-
-
-
- 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++;
- }
- }
-
-
-
-
- iteration++;
- if ((iteration & 0xf) == 0 &&
- (ustime()-start) > timelimit) return;
-
- } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
- }
- }