redis源码之ttl(三)
简介
redis可以设置key的ttl(time to live, 存活时长),当时间超过ttl了,就会删除key。通过源码阅读了解下面两个问题:
- redis如何保存ttl信息
- redis什么时候触发删除过期key的操作
源码
ttl的保存
在set key的时候设置或者更新ttl
#t_string.c set命令的功能函数
# 参数expire是过期时间,类型是robj,但是可以从中读取到long long类型的值,表示的是相对当前时间
# unit是时间单位,表示是毫秒或者秒为单位,如果是以秒为单位,就要乘以1000转为毫秒为单位
# 所以redis的ttl的精度是毫秒级别的
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
long long milliseconds = 0; /* initialized to avoid any harmness warning */
if (expire) {
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
return;
if (milliseconds <= 0) {
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
return;
}
if (unit == UNIT_SECONDS) milliseconds *= 1000;
}
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
{
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
return;
}
genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
server.dirty++;
// redis记录的是绝对时间,所以这里加上了当前时间mstime()
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
"expire",key,c->db->id);
addReply(c, ok_reply ? ok_reply : shared.ok);
}
// db.c 设置key的过期时间
// 将key放入expires字典中,dictEntry的v字段记录了键值过期的绝对时间
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de;
/* Reuse the sds from the main dict in the expire dict */
kde = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
// 在字典db->expires中查找kde->key(如果不存在key,就为key创建dictEntry)并
// 返回对应的dictEntry *
de = dictAddOrFind(db->expires,dictGetKey(kde));
// de->v.s64 = when 将when放入de, 所以在expire字典中,每个dictEntry存放了key的过期时间
// 如果没有设置ttl,就不会把key放入expires字典中
dictSetSignedIntegerVal(de,when);
int writable_slave = server.masterhost && server.repl_slave_ro == 0;
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
rememberSlaveKeyWithExpire(db,key);
}
// dict.h
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
// 在expires字典中, dictEntry的v.s64记录了key的绝对过期时间,单位是毫秒
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
删除过期key
过期key的删除可以分为被动删除和主动删除:
- 被动删除是访问到key的时候发现key过期了,就删除
- 主动删除是通过定时任务周期性的遍历检查expires字典中是否存在过期的key。
被动删除源码:
//db.c 下面是删除过期key的功能函数
int expireIfNeeded(redisDb *db, robj *key) {
// 如果key没有过期
if (!keyIsExpired(db,key)) return 0;
// key过期了
/* If we are running in the context of a slave, instead of
* evicting the expired key from the database, we 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. */
// 如果是备机上执行这个函数, 并不会在备机上删除key
// 备机删除是等主机发送DEL命令过来触发删除操作
if (server.masterhost != NULL) return 1;
/* Delete the key */
// 计数
server.stat_expiredkeys++;
// 通知从节点和AOF文件删除key
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
// 如果服务器配置了lazyfree_lazy_expire, 那就异步删除,否则同步删除
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
// 通知watch key的客户端key已经更改,会导致exec命令失败
// 通知缓存了key的客户端,缓存失效
if (retval) signalModifiedKey(NULL,db,key);
return retval;
}
// 只要访问key,都会判断key是否过期,如果过期了就删除key
// 比如插入key之前,检查发现key已经存在且过期,就删除过期的key
// 下面是会调用expireIfNeeded的函数
// get命令调用
lookupKeyReadWithFlags() db.c
// set命令调用
lookupKeyWriteWithFlags() db.c
// 随机获取key调用
dbRandomKey
// scan命令调用
scanGenericCommand
主动删除过期的key
// 函数名 文件名
// 定时任务,每秒执行10次
serverCron server.c
databasesCron server.c
activeExpireCycle expire.c
activeExpireCycleTryExpire expire.c
// 获取key的过期时间
dictGetSignedIntegerVal
// 删除key
deleteExpiredKeyAndPropagate
// 异步删除
dbAsyncDelete
// 同步删除
beforeSleep server.c
activeExpireCycle expire.c
expire字典的检查和回收key分为慢速和快速两种方式:
- 慢速是通过定时任务,系统每0.1秒会执行一次serverCron, 所以也就是每0.1秒会检查一次expire字典,然后清理过期的key,花更多的时间,更少的频率。
- 快速每个event loop cycle都会执行一次快速的expire字典检查清理,花更少的时间,更高的频率。
- 这个工作是累积的,均匀的处理每个数据库,并且每个数据库都会记录上次处理到哪里了,下次接着上次的进行处理。
查看expire统计数据
192.168.1.239:6379> info stats
# Stats
...
expired_keys:0
expired_stale_perc:0.00
expired_time_cap_reached_count:0
expire_cycle_cpu_milliseconds:43
evicted_keys:0
...
# 查看相关配置
192.168.1.239:6379> config get *expire*
1) "lazyfree-lazy-expire"
2) "no"
3) "active-expire-effort"
4) "1"

浙公网安备 33010602011771号