Redis中键的过期删除策略

使用Redis时我们可以使用EXPIRE或EXPIREAT命令给key设置过期删除时间,结构体redisDb中的expires字典保存了所有key的过期时间,这个字典(dict)的key是一个指针,指向redis中的某个key对象,过期字典的value是一个保存过期时间的整数。

/* Redis database representation. There are multiple databases identified

* by integers from 0 (the default database) up to the max configured

* database. The database number is the 'id' field in the structure. */typedefstructredisDb {    dict *dict;/* The keyspace for this DB */dict *expires;/* 过期字典*/dict *blocking_keys;/* Keys with clients waiting for data (BLPOP) */dict *ready_keys;/* Blocked keys that received a PUSH */dict *watched_keys;/* WATCHED keys for MULTI/EXEC CAS */structevictionPoolEntry *eviction_pool;/* Eviction pool of keys */intid;/* Database ID */longlongavg_ttl;/* Average TTL, just for stats */} redisDb;

设置过期时间

不论是EXPIRE,EXPIREAT,还是PEXPIRE,PEXPIREAT,底层的具体实现是一样的。在Redis的key空间中找到要设置过期时间的这个key,然后将这个entry(key的指针,过期时间)加入到过期字典中。

voidsetExpire(redisDb *db, robj *key,longlongwhen){    dictEntry *kde, *de;/* Reuse the sds from the main dict in the expire dict */kde = dictFind(db->dict,key->ptr);    redisAssertWithInfo(NULL,key,kde !=NULL);    de = dictReplaceRaw(db->expires,dictGetKey(kde));    dictSetSignedIntegerVal(de,when);}

13465705-8e44fc57e9e0943b.jpg!web

过期删除策略

如果一个key过期了,何时会被删除呢?在Redis中有两种过期删除策略:(1)惰性过期删除;(2)定期删除。接下来具体看看。

惰性过期删除

Redis在执行任何读写命令时都会先找到这个key,惰性删除就作为一个切入点放在查找key之前,如果key过期了就删除这个key。

13465705-898ad29a81678259.jpg!web

robj *lookupKeyRead(redisDb *db, robj *key) {    robj *val;    expireIfNeeded(db,key);// 切入点val= lookupKey(db,key);if(val== NULL)        server.stat_keyspace_misses++;elseserver.stat_keyspace_hits++;returnval;}

定期删除

key的定期删除会在Redis的周期性执行任务(serverCron)中进行,而且是发生Redis的master节点,因为slave节点会通过主节点的DEL命令同步过来达到删除key的目的。

13465705-6a61e4eb7eefdaad.jpg!web

依次遍历每个db(默认配置数是16),针对每个db,每次循环随机选择20个(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)key判断是否过期,如果一轮所选的key少于25%过期,则终止迭次,此外在迭代过程中如果超过了一定的时间限制则终止过期删除这一过程。

for(j =0; j < dbs_per_call; j++) {intexpired;    redisDb *db = server.db+(current_db % server.dbnum);/* Increment the DB now so we are sure if we run out of time

    * in the current DB we'll restart from the next. This allows to

    * distribute the time evenly across DBs. */current_db++;/* Continue to expire if at the end of the cycle more than 25%

    * of the keys were expired. */do{unsignedlongnum, slots;longlongnow, ttl_sum;intttl_samples;/* 如果该db没有设置过期key,则继续看下个db*/if((num = dictSize(db->expires)) ==0) {            db->avg_ttl =0;break;        }        slots = dictSlots(db->expires);        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. */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. */expired =0;        ttl_sum =0;        ttl_samples =0;if(num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)            num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;// 20while(num--) {            dictEntry *de;longlongttl;if((de = dictGetRandomKey(db->expires)) ==NULL)break;            ttl = dictGetSignedIntegerVal(de)-now;if(activeExpireCycleTryExpire(db,de,now)) expired++;if(ttl >0) {/* We want the average TTL of keys yet not expired. */ttl_sum += ttl;                ttl_samples++;            }        }/* Update the average TTL stats for this database. */if(ttl_samples) {longlongavg_ttl = ttl_sum/ttl_samples;/* Do a simple running average with a few samples.

            * We just use the current estimate with a weight of 2%

            * and the previous estimate with a weight of 98%. */if(db->avg_ttl ==0) db->avg_ttl = avg_ttl;            db->avg_ttl = (db->avg_ttl/50)*49+ (avg_ttl/50);        }/* 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. */iteration++;if((iteration &0xf) ==0) {/* 每迭代16次检查一次 */longlongelapsed = ustime()-start;            latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);if(elapsed > timelimit) timelimit_exit =1;        }// 超过时间限制则退出if(timelimit_exit)return;/* 在当前db中,如果少于25%的key过期,则停止继续删除过期key */}while(expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);}

总结

惰性删除:读写之前判断key是否过期

定期删除:定期抽样key,判断是否过期

欢迎Java工程师朋友们加入Java进阶高级架构群:855355016

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值