Redis深度历险-过期删除
保存过期时间
Redis支持通过
TTL
和PTTL
命令来查询剩下的存活时间
typedef struct redisDb {
......
dict *expires; /* Timeout of keys with a timeout set */
......
} redisDb;
在Redis中每一个数据库中有一个专门存储过期时间的字典,不管是通过什么命令设置的过期时间,内部存储的都是过期的毫秒数时间戳
Redis的过期删除策略
常用的删除策略是惰性删除和定时删除,Redis是两者结合
惰性删除
在
db.c/expireIfNeeded
函数就是用来处理过期键,在命令真正执行之前会过滤删除掉过期的键
int expireIfNeeded(redisDb *db, robj *key) {
//如果键没有过期,返回0
if (!keyIsExpired(db,key)) return 0;
//非主机或者pause状态则不会进行删除
if (server.masterhost != NULL) return 1;
if (checkClientPauseTimeoutAndReturnIfPaused()) return 1;
//lazyfree_lazy_expire配置决定同步删除还是异步删除
if (server.lazyfree_lazy_expire) {
dbAsyncDelete(db,key);
} else {
dbSyncDelete(db,key);
}
server.stat_expiredkeys++;
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
signalModifiedKey(NULL,db,key);
return 1;
}
long long getExpire(redisDb *db, robj *key) {
dictEntry *de;
//如果key没有过期时间返回-1
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
//字符串转换为整数
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
return dictGetSignedIntegerVal(de);
}
在各种命令中最终都会调用到此函数中,逻辑上只是从expire
中查询并比较
定时删除策略
定时删除的逻辑实现在
server.c/activeExpireCycle
中,在Redis的定时函数server.c/serverCron
中被调用
void activeExpireCycle(int type) {
......
//记录此次操作的是哪个数据库
static unsigned int current_db = 0; /* Next DB to test. */
//遍历16个数据库,对每一个都删除一定的过期key
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
.......
定时删除的代码比较复杂,这里简述一下定时删除的规则:
- 每一次定时删除都会对尝试所有的数据库进行删除操作
- Redis的定时删除操作是有时间限制的,超过时间会退出此函数
- Redis会记录当前过期的操作进度,包括操作的数据库、操作的过期键游标
时间事件
Redis是基于多路复用的事件模型,是支持实现定时器的;但是Redis中的时间事件是使用一个顺序链表存储的,不适用于大量的定时任务处理
主备模式的删除策略
在主备的部署模式下,备机不会删除过期键;但是给客户端的返回值还是尽量保持一致,视作已过期删除