引言

Redis作为一个高性能的内存NoSQL数据库,其容量受到最大内存限制的限制。为了防止一次性清理大量过期Key导致Redis服务受影响,Redis只在空闲时清理过期Key。

Redis过期时间

设置过期时间

redis有四种命令可以用于设置键的生存时间和过期时间:

1
2
3
4
EXPIRE <KEY> <TTL> : 		将键的生存时间设为 ttl 秒
PEXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 毫秒
EXPIREAT <KEY> <timestamp> : 将键的过期时间设为 timestamp 所指定的秒数时间戳
PEXPIREAT <KEY> <timestamp>: 将键的过期时间设为 timestamp 所指定的毫秒数时间戳.

返回值

一个整数值1或0,如下:

  • 如果成功地为该键设置了超时时间,返回 1
  • 如果键不存在或无法设置超时时间,返回 0

保存过期时间

redis中key的过期时间和生存时间保存方式:在数据库结构redisDb中的expires字典中保存了数据库中所有键的过期时间,称expire这个字典为过期字典。
(1)过期字典是一个指针,指向键空间的某个键对象。
(2)过期字典的值是一个longlong类型的整数,这个整数保存了键所指向的数据库键的过期时间–一个毫秒级的 UNIX 时间戳。

下图是一个带过期字典的数据库例子:

从以上结构中可以看到expire字典(过期字典)和dict字典(数据库键空间,保存着数据库中所有键值对)是并列的,由此可见expire字典的重要性。

移除过期时间

PERSIST命令可以移除一个键的过期时间:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> set message "hello"
OK
127.0.0.1:6379> expire message 60
(integer) 1
127.0.0.1:6379> ttl message
(integer) 54
127.0.0.1:6379> persist message
(integer) 1
127.0.0.1:6379> ttl message
(integer) -1

persist命令就是expire命令的反命令,这个函数在过期字典中查找给定的键,并从过期字典中移除。
比如在数据库当前状态(如上图所示),当给book这个key移除过期时间:

1
2
redis> persist book
(integer) 1

数据库将更新成如下状态:

可以从图中看到,当PERSIST book命令执行之后,过期字典中的 book 键消失了。

计算并返回剩余生存时间

ttl命令以秒为单位返回指定键的剩余生存时间。pttl毫秒返回。两个命令都是通过计算当前时间和过期时间的差值得到剩余生存期的。

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set name rainbowhorse
OK
127.0.0.1:6379> expire name 60
(integer) 1
127.0.0.1:6379> ttl name
(integer) 57
127.0.0.1:6379> ttl name
(integer) 27
127.0.0.1:6379> pttl name
(integer) 23839
127.0.0.1:6379>

过期键的删除策略

如果一个键是过期的,它的怎么从内存中消失的,是什么时候被删除的?

redis有三种不同的删除策略:

  • 定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。
  • 立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。
  • 惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。

由此可见,第二种为被动删除,第一种和第三种为主动删除,且第一种实时性更高。下面对这三种删除策略进行具体分析。

定时删除

  • 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
  • 优点:保证内存被尽快释放
  • 缺点:
    • 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
    • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
    • 没人用

定期删除

  • 含义:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key操作
  • 优点:
    • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理”定时删除”的缺点
    • 定期删除过期key–处理”惰性删除”的缺点
  • 缺点
    • 在内存友好方面,不如”定时删除”
    • 在CPU时间友好方面,不如”惰性删除”
  • 难点
    • 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)

定期删除可以通过:

  • 第一、配置redis.conf 的hz选项,默认为10 (即1秒执行10次,100ms一次,值越大说明刷新频率越快,最Redis性能损耗也越大)
  • 第二、配置redis.conf的maxmemory最大值,当已用内存超过maxmemory限定时,就会触发主动清理策略

惰性删除

  • 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
  • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
  • 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)

定时删除和定期删除为主动删除:Redis会定期主动淘汰一批已过去的key。

惰性删除为被动删除:用到的时候才会去检验key是不是已过期,过期就删除。

redis使用的策略

redis使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用

最佳实践

  • 不要放垃圾数据,及时清理无用数据。实验性的数据和下线的业务数据及时删除。
  • key尽量都设置过期时间。对具有时效性的key设置过期时间,通过redis自身的过期key清理策略来降低过期key对于内存的占用,同时也能够减少业务的麻烦,不需要定期手动清理了。
  • 单Key不要过大。给用户排查问题时遇到过单个string的value有43M的,也有一个list 100多万个大成员占了1G多内存的。这种key在get的时候网络传输延迟会比较大,需要分配的输出缓冲区也比较大,在定期清理的时候也容易造成比较高的延迟. 最好能通过业务拆分,数据压缩等方式避免这种过大的key的产生。
  • 不同业务如果公用一个业务的话,最好使用不同的逻辑db分开。从上面的分析可以看出,Redis的过期Key清理策略和强制淘汰策略都会遍历各个db。将key分布在不同的db有助于过期Key的及时清理。另外不同业务使用不同db也有助于问题排查和无用数据的及时下线。

参考

redis的过期时间和过期删除机制