0%

Redis如何删除过期键

通过EXPIRE,PEXPIRE,SETEX等指令,Redis可以为数据库中的某键设置生存时间,在经过指定时间之后,服务器就会删除生存时间为0的键。

设置过期时间

保存过期时间

redisDb结构的expires字典保存了数据库中所有键的过期时间。

1
2
3
4
5
6
7
typedef struct redisDb {
// 数据库键空间,保存着数据库中的所有键值对
dict *dict; /* The keyspace for this DB */
// 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
dict *expires; /* Timeout of keys with a timeout set */
// ...
} redisDb;

过期字典的键是一个指针指向键空间中的某一个键对象,值是一个long long类型的整数,保存了毫秒精度的UNIX时间戳。当执行设置过期时间的命令时,服务器会在数据库的过期字典中关联给定的数据库键以及过期时间。

移除过期时间

PERSIST命令可以移除一个键的过期时间。当需要移除过期时间时,会从expires字典之中移除给定键的键值对关联。

返回过期时间

TTL和PTTL命令可以返回秒或毫秒为单位的剩余生存时间。其都是通过计算两个键的过期时间与当前时间之差来实现的。

过期键删除策略

可能的删除策略

定时删除

通过在设置键的同时,创建一个定时器。在定时器结束时,立即删除该键。该策略对内存友好,可以保证过期键最快的被删除。但是在比较多过期键的情况下,会很占用CPU资源,导致服务器的响应时间降低。

惰性删除

惰性删除指的是当从键空间中获取值时,检查该键是否过期,如果过期的话,则删除该键,否则返回该键。惰性删除策略不会在删除其他无关键上花费CPU时间,属于CPU友好型策略。但是如果一个键已经过期但是一致没有被访问到,那么该键永远不会过期,所占用的内存永远不会释放。

定期删除

每隔一段时间,对数据库进行检查,删除其中的过期键。其难点在于确定执行的时长与频率。如果执行的太频繁,会导致占用过多CPU,如果执行的太少,会导致内存浪费的情况。

Redis中的删除策略实现

Redis中整合了惰性删除和定期删除两种策略,取得了合理使用CPU时间和内存空间之间的平衡。

惰性删除实现

所有读写redis命令在执行之前都会调用惰性删除的代码进行判断,惰性删除的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
* 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
*
* 返回 0 表示键没有过期时间,或者键未过期。
* 返回 1 表示键已经因为过期而被删除了。
*/
int expireIfNeeded(redisDb *db, robj *key) {
// 取出键的过期时间
mstime_t when = getExpire(db,key);
mstime_t now;
// 没有过期时间
if (when < 0) return 0;
// 如果服务器正在进行载入,那么不进行任何过期检查
if (server.loading) return 0;
// 当前时间
now = server.lua_caller ? server.lua_time_start : mstime();
// 当服务器运行在 replication 模式时
// 附属节点并不主动删除 key
// 它只返回一个逻辑上正确的返回值
// 真正的删除操作要等待主节点发来删除命令时才执行
// 从而保证数据的同步
if (server.masterhost != NULL) return now > when;
// 运行到这里,表示键带有过期时间,并且服务器为主节点
// 如果未过期,返回 0
if (now <= when) return 0;
server.stat_expiredkeys++;
// 向 AOF 文件和附属节点传播过期信息
propagateExpire(db,key);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
"expired",key,db->id);
// 将过期键从数据库中删除
return dbDelete(db,key);
}

如果键已经过期,那么该函数会将输入的键从数据库之中删除,否则不做任何动作。当处理后的键仍然存在时,命令会按照存在的情况继续执行,否则按照不存在的情况执行。

定期删除实现

每当Redis的周期操作函数serverCron函数执行时,会调用activeExpireCycle函数进行过期键的清理。其会在规定的时间内,分次遍历数据库中的各个数据库,从expires字典中随机检查一部分过期时间,并删除其中的过期键。在扫描的时候,会提供一个全局变量记录上一次扫描的进度,并在下一次调用时,接着上一次的进度进行新一轮的检查工作。

AOF,RDB和复制功能处理过期键

AOF文件处理

当一个键已经过期时并被删除时,Redis会像AOF日志之中添加一条DEL命令,来记录该键已经被删除。在进行AOF重写的时候,程序会对数据库中的键进行检查,已过期的键不会保存到重写以后的AOF日志之中。

RDB文件处理

在生成RDB文件的时候,如果一个键已经过期,那么不会被保存到RDB文件之中。在载入RDB文件的时候,如果以主服务器的方式运行,那么过期的键不会被载入到数据库之中。如果以从服务器的方式运行,那么无论键是否过期都会被载入,因为在于主服务器同步的时候,从服务器的数据就会清空,所以对从服务器也没有影响。

复制功能处理

当处于服务器处于复制模式下时,服务器的过期删除键由主服务器控制。在主服务器删除一个过期键之后,会向从服务器发送一个DEL命令,告诉从服务器删除该键。从服务器即使键到达了过期时间也不会删除,知道收到DEL命令时才进行删除。通过主服务器统一的删除过期键可以保证主从一致性。