一、关于Redis内存回收

Redis是基于内存操作的非关系型数据库,Redis中提供了多种内存回收策略,当内存容量不足时,为了保证程序的运行,这时就不得不淘汰内存中的一些对象,释放这些对象占用的空间,那么选择淘汰哪些对象呢?

Redis的内存回收,主要围绕以下两种方式:

  • 1、Redis过期策略:删除已经过期的数据。
  • 2、Redis淘汰策略:内存使用到达maxmemory上限时触发内存淘汰数据。

注意:过期策略和淘汰策略是两种不同的概念。

二、Redis过期策略

在Redis中,提供了expire命令设置一个键的过期时间,到期之后Redis会自动删除它,这个在我们的实际使用过程中用的非常多。

Redis中设置过期时间有如下两种方式:

  • 1、expire命令:expire key seconds(先set key,然后设置过期时间。其中seconds 参数表示键的过期时间,单位为秒。expire 返回值为1表示设置成功,0表示设置失败或者键不存在)
  • 2、setex命令:setex key seconds value(设置键的同时,直接设置过期时间)

expire命令的seconds单位为秒,最小精确至1秒,如果想要更精确的控制键的过期时间,可以使用pexpire命令,pexpire命令的单位是毫秒。pexpire key 1000与expire key 1相等。

Redis 过期时间相关命令

1、设置过期时间

Redis 提供了四个命令来设置过期时间(生存时间):

  • EXPIRE <key> <ttl>:表示将键 key 的生存时间设置为 ttl 秒;
  • PEXPIRE <key> <ttl>:表示将键 key 的生存时间设置为 ttl 毫秒;
  • EXPIREAT <key> <timestamp>:表示将键 key 的生存时间设置为 timestamp 所指定的秒数时间戳;
  • PEXPIREAT <key> <timestamp>:表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。

在Redis内部实现中,前面三个设置过期时间的命令最后都会转换成最后一个PEXPIREAT 命令来完成。

2、移除过期时间

  • PERSIST <key>:表示将 key 的过期时间移除。

3、查看键的剩余过期时间

  • TTL <key>:以秒的单位返回键 key 的剩余生存时间;
  • PTTL <key>:以毫秒的单位返回键 key 的剩余生存时间。

三种过期策略

  • 1、定时删除: 在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。
  • 优点:保证内存被尽快释放
  • 缺点:若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key。
  • 2、惰性删除: key过期的时候不删除, 每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
  • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)。
  • 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)。
  • 3、定期删除: 每隔一段时间执行一次删除过期key操作。
  • 优点:通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点,定期删除过期key–处理"惰性删除"的缺点。
  • 缺点:在内存友好方面,不如"定时删除",在CPU时间友好方面,不如"惰性删除"。
    难点:合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)。

Redis采用的过期策略:惰性删除+定期删除。

1、Redis定期删除策略

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

  • Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每 100ms 进行一次过期扫描,随机抽取 20 个 key,删除这 20 个key中过期的key。
  • 如果过期的 key 比例超过 1/4,就重复上述步骤,继续删除。

为什不扫描所有的 key?

  • Redis 是单线程,全部扫描岂不是卡死了。而且为了防止每次扫描过期的 key 比例都超过 1/4,导致不停循环卡死线程,Redis 为每次扫描添加了上限时间,默认是 25ms。
    如果在同一时间出现大面积 key 过期,Redis 循环多次扫描过期词典,直到过期的 key 比例小于 1/4。这会导致卡顿,而且在高并发的情况下,可能会导致缓存雪崩。
从库的过期策略

从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。

因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在。

注意

  • Redis 的定期删除策略并不是一次运行就检查所有的库、所有的键,而是随机检查一定数量的键。
  • 定期删除函数的运行频率,在 Redis2.6 版本中,规定每秒运行 10 次,大概 100ms 运行一次。在 Redis2.8 版本后,可以通过修改配置文件 redis.conf 的 hz 选项来调整这个次数:
...
# The range is tetween 1 and 500, however a value over 100 is usually not
# a good idea. Most users should use the default of 10 and raise this up to
# 100 only in environments where very low latency is requiried.
hz 10
...

在这个参数的上面注释可以看出,建议不要将这个值设置超过100,一般使用默认的10,只有当在需要非常低延迟的场景才设置为100。