redis 过期时间

145 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

数据库如何保存键的生存时间和过期时间,以及服务器如何自动删除那些带有生存时间和过期时间的键

生存时间和过期时间有四种,分别是expire、pexpire、expireat、pexpireat。但是他们都是使用pexpireat命令来实现的。其他三个都可以转换为pexpireat

过期时间设置:

set key value
expire key 5000

image-20220306111016182.png

保存过期时间

redisDb结构的expires字典保存了数据库中所有键的过期时间。键是指针,指向一个long类型的整数(unix时间戳)。

当客户端执行过期时间命令的时候,服务器会在数据库的过期字典中关联给定的数据库键和过期时间

例如:

expire message 1391234400000

过期字典树就变成了这样

expireat原理:

  1. 如果给定的键不存在于键空间,那么不能设置过期时间
  2. 存在键空间,在过期字典中关联键和过期时间
  3. 过期时间设置成功,返回1

移除过期时间

使用persist命令在过期字典中查找给定的键,并解除键和值在过期字典中的关联

persist原理

  1. 如果键不存在,或者键没有设置过期时间,那么直接返回
  2. 如果键存在,那么移除过期字典中给定键的键值对关联
  3. 键的过期时间移除成功

计算并返回剩余生存时间:ttl和pttl命令都是通过计算键的过期时间和当前时间之间的差毫秒为单位

过期键的判定

原理:

  1. 获取键的过期时间
  2. 如果键没有设置过期时间,那么直接返回false
  3. 获取当前时间的unix时间戳
  4. 检查当前时间是否大于键的过期时间
  • 注意:其实也可以使用ttl和pttl命令,返回值大于等于0就没过期,但是实际上直接访问字典比执行一个命令快,所以使用访问字典

过期键删除策略(什么时候删除)

  • 定时删除:创建一个定时器,定时器在键的过期时间来临时,立即执行对键的删除策略

对内存最友好,因为该策略能保证过期键尽可能快地被删除,从而释放内存,但缺点是对CPU时间不友好,如果过期键比较多,删除过期键的行为会占用相当一部分CPU时间,从而对服务器的响应时间和吞吐量造成影响。 并且创建一个定时器需要用到时间事件,而当前时间事件的实现方式是无序链表,查找一个事件的时间复杂度为O(N)无法高效处理大量时间事件

  • 惰性删除:每次从键空间中获取键的时候检查键是否过期,过期则删除,不过期就返回键

对CPU时间最友好,只会在取出键的时候才对键进行过期检查,只删除当前的键,不删除无关过期键,所以不花费多余的CPU时间。但是如果没有被访问的过期键永远不会被删除,也就是内存泄漏

  • 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。并且通过限制删除键的时长和频率来减少删除操作对CPU时间的影响。每秒扫描10次(扫描时间不超过25毫秒),过期扫描,过期扫描不会遍历过期字典的所有key,而是采用一种贪心策略:

    1. 从过期字典中随机20个key
    2. 删除这20个key中已经过期的key
    3. 如果过期的key比率超过1/4,重复步骤1

是前面两种策略的折中方案,难点是确定删除操作执行的时长和频率

Redis采用的是惰性删除和定期删除

惰性删除的实现:

通过db.c/expireIfNeeded函数实现,对输入键进行检查,如果输入键过期,那么expireIfNeeded函数将输入键从数据库中删除,如果未过期就不动作。这里面分为两种情况,键存在或者不存在,不存在直接返回空

image-20220306131059024.png

定期删除策略的实现:

redis.c/activeExpireCycle函数实现,每当Redis的服务器周期性地操作redis.c/serverCron函数执行时,activeExpireCycle函数就会被调用,在规范的时间内多次遍历服务器的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并且删除其中的过期键

原理:

默认每次检查数据库的数量:16

默认每个数据库检查的键数量:20

全局变量current_db=0用来记录检查进度

遍历各个数据库,如果current_db的值等于服务器的数据库数量,说明已经遍历了服务器的所有数据库一次,重置为0,开始新一轮遍历

遍历的步骤:

  1. 获取当前要处理的数据库

  2. 数据库索引增1,指向下一个要处理的数据库

  3. 检查数据库键

    1. 如果数据库中没有一个键带有过期时间,直接跳过
    2. 如果有键带有过期时间,随机获取一个带有过期时间的键
    3. 检查键是否过期,如果过期就删除调
    4. 如果已经到达时间上限了,就停止处理

\