Redis中可以通过EXPIRE命令和PEXPIRE命令可以给键设置生存时间(Time To Live TTL),也可以指定什么级别,毫秒级别或者秒级别,在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键
set msg value
expire msg 5
get msg
也可以使用SETEX命令,在设置字符串的同时又为键设置过期时间,但这个命令只能用于字符串键,其实现的原理与分别执行set和expire是一样的。
可以使用expireat命令或者pexpireat命令,以秒或毫秒值精度给数据库中的某个键设置过期时间
可以通过TTL命令和PTTL命令接受一个带有生存时间或则过期时间的键,返回这个键的剩余生存时间,也就是返回距离这个键被服务器自动删除还有多长时间
设置过期时间
Redis总共有4个不同的命令来为键设置过期时间
ttl是指生存时间,pttl是指过期时间,用时间戳表示
-
expire key seconds 用于将键key的生存时间设置为ttl秒,ttl即seconds
-
pexpire key milliseconds 用于将新键的生存时间设置为ttl毫秒
-
expireat key timestamp 用于将新键的过期时间设置为pttl秒时间戳,即timestamp所指定的秒数时间戳
-
pexpireat key milliseconds-timestamp 用于将新键key的过期时间设为pttl毫秒时间戳,即timestamp所指定的毫秒数时间戳
虽然有多种命令去实现设置过期时间,但实际上都是基于pexpireat命令来实现的,也就是说,无论客户端执行的是上面4条命令的哪一条命令,最终都会转换成执行pexpireat命令,实现其实就是根据命令的不同,将ttl改为pttl,或将秒改为毫秒,然后再用改变好的参数去执行expireat操作
保存过期时间
redisDB结构(数据库)的expires字典保存了数据库所有键的过期时间(使用字典去实现保存过期时间),称这个字典为过期字典
- 过期字典的键其实是一个指针,保存的是键空间里面的键值对对象
- 过期字典的值其实是一个Long long类型的整数,这个整数保存了过期时间(一个毫秒精度的unix时间戳)
typedef struct redisDB(
//键空间
dict *keyspace;
//过期字典
dict *expires;
)redisDB;
移除过期时间
可以使用命令来移除指定键的过期时间
//移除指定键的过期时间
persist key
PERSIST其实就是PEXPIREAT的反向操作,PERISIST在过期字典中查找给定的键,然后解除键和值在过期字典中的关联,也就是在过期字典中删除这个键值对
计算并返回剩余生存时间
前面提到过,可以使用TTL命令以秒为单位返回键的剩余生存时间,而PTTL命令则以毫秒为单位返回键的剩余生存时间
ttl key
pttl key
TTL和PTTL这两个命令都是通过计算键的过期时间和当前时间之间的差来实现的
PTTL的实现原理
前面已经说过,设置过期时间的命令最终都会变成pexpireat,使用的参数是毫秒级别的unix时间戳,所以只要通过设置的unix时间戳减去当前时间的时间戳,就可以得到剩余的生存时间(都要转换成毫秒级别)
TTL的实现原理
前面与PTTL一样,只不过返回的时候将毫秒转换成秒而已。
特殊返回值
键不在数据库时候,会返回**-2**
键没有设置过期时间,会返回**-1**
过期键的判定
通过过期字典,Redis通过以下步骤来判定过期键
- 检查给定键是否在过期字典中,存在就取键的过期时间
- 检查当前UNIX时间戳是否大于键的过期时间(UNIX时间戳),如果是的话,那么键就已经过期,否则的话,键未过期。
过期键的删除策略
当一个键过期的时候,Redis将会根据删除策略去决定删除过期键的时间和方式
总共有三种删除策略
- 定时删除:在设置键的过期时间的同时,设置一个定时器,让定时器在键的过期时间来临时,会立即执行对键的删除动作
- 惰性删除:放任键过期不管,但是用户从键空间去取键的时候,都会检查取得的键是否过期,如果过期的话,就删除该键,并且返回nil,如果没过期的话,就返回该键
- 定期删除:每隔一段时间就去对数据库进行检查,删除里面的过期键,要删除多少过期键或是对哪些数据库进行检查,由使用的算法规定
定时删除
定时删除的优点就是可以第一时间删除过期的键,这对于内存来说是十分友好的,可以及时释放不要的对象来节约内存
但定时删除的缺点也是第一时间去删除过期的键,假如存在大量的键都在同一时间过期,那么在这一段时间就会占用一部分的CPU,这段时间的服务器性能就会下降,降低了数据的吞吐量,严重的话会发生停止服务的行为。
除此之外,创建定时器需要用到Redis服务器中的时间事件,而时间事件的实现方式为无序链表,要找到某一刻的时间事件需要遍历无序链表,时间复杂度为 O ( N ) O(N) O(N)
因此这是不现实的
惰性删除
惰性删除对于CPU的占用是友好的,只有要找键值对的时候才进行判断删除,实现了就是到了万不得已的时候,才会去删除该键,并且不会去删除其他键,对CPU的占用可以是忽略不计
但缺点也就是要到万不得已的时候,才去删除,这对内存是不友好的,假如键过期了,然后用户那边不访问,这个键就会一直在内存中,占着资源,如果过期键很多很多,而且又不会再被访问,那么占用的内存就更多了。
对于内存数据库来说,这肯定也会造成影响
定期删除
定期删除就比较综合了,综合了定时删除和惰性删除的优点
- 定时删除占用CPU,惰性删除占用内存
定期删除是每隔一定时间,对指定数据库,删除指定数量的过期键
- 每隔一定时间才去执行,限制删除的数量,和扫描的数据库,减少了CPU的占用
- 定期去删除过期键,节约了内存
但实现定期删除是有难点的,一定时间具体是多久,指定数量又是多少,这些指标一旦偏大偏小,也还是会引发占用CPU、占用内存的问题出现,所以具体的实现也是要依靠具体情况去判断的。