[TOC]
设置过期时间的常见方式
1 | expire key ttl |
过期键值对的删除有三种策略
定时删除
设置一个定时器和回调函数,时间一到就调用回调函数删除键值对。优点是及时删除,缺点是需要为每个键值对都设置定时器,比较麻烦(其实可以用timer_fd的,参考muduo定时任务的实现)
惰性删除
只有当再次访问该键时才判断是否过期,如果过期将其删除。优点是不需要为每个键值对进行时间监听,缺点是如果这个键值对一直不被访问,那么即使过期也会一直残留在数据库中,占用不必要的内存
周期删除
每隔一段时间执行一次删除过期键值对的操作。优点是既不需要监听每个键值对导致占用CPU,也不会一直不删除导致占用内存,缺点是不容易确定删除操作的执行时长和频率
Redis采用惰性删除和周期删除两种策略,通过配合使用,服务器可以很好的合理使用CPU时间和避免内不能空间的浪费
redis 过期策略的实现
redis expire api
Redis是如何实现定时删除的,在数据库结构redisDb中,可以发现除了上篇提到的用于保存键值对的dict字典外,另有一个字典变量expires,实际上正是它保存着键和其过期时间(绝对时间)。当执行完SET命令后,两个字典的数据分布为:
1 | typedef struct redisDb { |
设置键的过期时间,setExpire
1 | void setExpire(client *c, redisDb *db, robj *key, long long when) { |
dictSetSignedIntegerVal是宏定义,设置键节点de的值为when。因为哈希节点中的值结构是联合,可以存储不同大小的数字,也可以通过void*指针存储其它类型,这里过期时间是long long类型,所以可以存在int64_t类型上。
获取键的过期时间,getExpire
1 | long long getExpire(redisDb *db, robj *key) { |
删除键的过期时间,removeExpire
1 | int removeExpire(redisDb *db, robj *key) { |
redis 过期删除的实现
Redis 采用惰性删除和周期删除两种策略,通过配合使用,服务器可以很好的合理使用CPU时间和避免内不能空间的浪费。
惰性删除
惰性删除是指在对每一个键进行读写操作时,先判断一下这个键是否已经过期,如果过期则将其删除。该操作由expireIfNeeded函数完成。
get 操作的流程
1 | int getGenericCommand(client *c) |
主从实现惰性删除的方法
- 如果当前请求是在 master 节点上,且当前的 key 已经过期,那么直接删除过期的 key。
- 如果当前请求是在 slave 节点上,且当前 key 已经过期, 直接返回 null,不做删除。(删除操作只能由 master 执行并同步给 slave 节点)
周期删除
Redis服务器会周期性地执行server.c/serverCron函数,在这个函数中执行的databasesCron函数会调用activeExpireCycle函数,这个函数在时间字典(expires)中随机选择若干键节点,判断其是否过期,如果过期则将其删除。
循环函数
1 | void activeExpireCycle(int type) { |
真正删除的代码
1 | // 真正删除一个 key, Helper function for the activeExpireCycle() function. |
总结
Redis 采用惰性删除和周期删除两种策略,通过配合使用,服务器可以很好的合理使用CPU时间和避免内不能空间的浪费。
参考
redis源码–key的过期策略: 这个源码有点老了,可以看最新的源码。
ASAP: as sonn as possible