在面试中遇到美女面试官时,咱们觉得面试会比较容易过,也能好好表现本身技术的时候了。然而却出现如下这一幕,当美女面试官据说你使用过Redis时,那么问题来了。面试
👩面试官:Q1,你知道Redis设置key过时时间的命令吗?redis
👧你:你坚决果断的巴拉巴拉说了一堆命令,以及用法,好比expire 等等命令算法
(🎈这时候你想问得那么简单?但真的那么简单吗?美女面试官停顿了一下,接着问)数据库
👩面试官:Q2,那你说说Redis是怎么实现过时时间设置呢?以及怎么判断键过时的呢?
👧你:(这时候想这还难不倒我),而后又巴拉巴拉的说一通,Redis的数据库服务器中redisDb数据结构以及过时时间的断定服务器
(🎈你又在想应该不会问了吧,换个Redis的话题了吧,那你就错了)数据结构
👩面试官:(抬头笑着看了看你)Q3,那你说说过时键的删除策略以及Redis过时键的删除策略以及实现?
🤦️你:这时你回答的就不那么流畅了,有时头脑还阻塞了。dom
(🎈这是你可能就有点蒙了,或者只知道一些过时键的删除策略,但具体怎么实现不知道呀,你觉得面试官的提问这样就完了吗?)函数
👩面试官:Q4,那你再说说其余环节中是怎么处理过时键的呢(好比AOF、RDB)?
🤦🤦你:...........学习
(🎈这更加尴尬了,知道的不全,也可能不知道,原本想好好表现,也想着面试比较简单,没想到会经历这些)this
为了不这尴尬的场景出现,那如今须要你记录下如下的内容,这样就能够在美女面试官面前好好表现了。
redis数据库在数据库服务器中使用了redisDb
数据结构,结构以下:
typedef struct redisDb {
dict *dict; /* 键空间 key space */
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 */
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
复制代码
其中,
key space
):dict字典用来保存数据库中的全部键值对expires
):保存数据库中全部键的过时时间,过时时间用UNIX
时间戳表示,且值为long long
整数EXPIRE \<key> \<ttl>
:命令用于将键key的过时时间设置为ttl秒以后PEXPIRE \<key> \<ttl>
:命令用于将键key的过时时间设置为ttl毫秒以后EXPIREAT \<key> \<timesramp>
:命令用于将key的过时时间设置为timrestamp所指定的秒数时间戳PEXPIREAT \<key> \<timesramp>
:命令用于将key的过时时间设置为timrestamp所指定的毫秒数时间戳设置过时时间:
redis> set Ccww 5 2 0
ok
redis> expire Ccww 5
ok
复制代码
使用redisDb结构存储数据图表示:
过时键的断定,其实经过过时字典进行断定,步骤:
因为定时删除会占用太多cpu时间,影响服务器的响应时间和吞吐量以及惰性删除浪费太多内存,有内存泄露的危险,因此出现一种整合和折中这两种策略的按期删除策略。
定时删除策略难点就是肯定删除操做执行的时长和频率:
删除操做执行得太频繁。或者执行时间太长,按期删除策略就会退化成为定时删除策略,以致于将cpu时间过多地消耗在删除过时键上。相反,则惰性删除策略同样,出现浪费内存的状况。 因此使用按期删除策略,须要根据服务器的状况合理地设置删除操做的执行时长和执行频率。
Redis服务器结合惰性删除和按期删除两种策略一块儿使用,经过这两种策略之间的配合使用,使得服务器能够在合理使用CPU时间和浪费内存空间取得平衡点。
Redis在执行任何读写命令时都会先找到这个key,惰性删除就做为一个切入点放在查找key以前,若是key过时了就删除这个key。
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
expireIfNeeded(db,key); // 切入点
val = lookupKey(db,key);
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
return val;
}
复制代码
经过expireIfNeeded
函数对输入键进行检查是否删除
int expireIfNeeded(redisDb *db, robj *key) {
/* 取出键的过时时间 */
mstime_t when = getExpire(db,key);
mstime_t now;
/* 没有过时时间返回0*/
if (when < 0) return 0; /* No expire for this key */
/* 服务器loading时*/
if (server.loading) return 0;
/* 根据必定规则获取当前时间*/
now = server.lua_caller ? server.lua_time_start : mstime();
/* 若是当前的是从(Slave)服务器
* 0 认为key为无效
* 1 if we think the key is expired at this time.
* */
if (server.masterhost != NULL) return now > when;
/* key未过时,返回 0 */
if (now <= when) return 0;
/* 删除键 */
server.stat_expiredkeys++;
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
复制代码
key的按期删除会在Redis的周期性执行任务(serverCron
,默认每100ms执行一次)中进行,并且是发生Redis的master
节点,由于slave
节点会经过主节点的DEL命令同步过来达到删除key的目的。
for (j = 0; j < dbs_per_call; j++) {
int expired;
redisDb *db = server.db+(current_db % server.dbnum);
current_db++;
/* 超过25%的key已过时,则继续. */
do {
unsigned long num, slots;
long long now, ttl_sum;
int ttl_samples;
/* 若是该db没有设置过时key,则继续看下个db*/
if ((num = dictSize(db->expires)) == 0) {
db->avg_ttl = 0;
break;
}
slots = dictSlots(db->expires);
now = mstime();
/*但少于1%时,须要调整字典大小*/
if (num && slots > DICT_HT_INITIAL_SIZE &&
(num*100/slots < 1)) break;
expired = 0;
ttl_sum = 0;
ttl_samples = 0;
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;// 20
while (num--) {
dictEntry *de;
long long ttl;
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) {
long long avg_ttl = ttl_sum/ttl_samples;
/样本获取移动平均值 */
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
iteration++;
if ((iteration & 0xf) == 0) { /* 每迭代16次检查一次 */
long long elapsed = 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);
}
复制代码
依次遍历每一个db(默认配置数是16),针对每一个db,每次循环随机选择20个(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
)key判断是否过时,若是一轮所选的key少于25%过时,则终止迭次,此外在迭代过程当中若是超过了必定的时间限制则终止过时删除这一过程。
生成RDB文件
程序会数据库中的键进行检查,已过时的键不会保存到新建立的RDB文件中
载入RDB文件
当服务器运行在复制模式下时,从服务器的过时键删除动做由主服务器控制的,这样的好处主要为了保持主从服务器数据一致性:
最后可关注公众号【Ccww笔记】,一块儿学习,天天会分享干货,还有学习视频干货领取!