当前位置:首页 > 问答 > 正文

重构Redis时那些键值过期前的细节和机制你知道吗?

当我们谈论重构或维护使用Redis的系统时,理解键值对(key)是如何在过期前被处理和删除的,是避免出现诡异bug的关键,这不仅仅是设置一个过期时间(TTL)那么简单,背后有一套协同工作的机制,这些机制决定了键何时被删除,以及删除操作对系统性能的影响。

最核心的一点是:Redis并不会在键过期时间到达的那一刻,像闹钟一样“准时”地立即删除它,如果这样做,每当有大量键同时过期时,Redis可能会因为要执行大量删除操作而瞬间卡顿,影响服务,Redis采用了两种主要的策略来应对这个问题:惰性删除定期删除,这两种策略是相辅相成的。

惰性删除(Lazy Expiration),顾名思义,就是一种比较“懒惰”的策略,它的原则是:只有当客户端主动尝试访问一个键的时候,Redis才会去检查这个键是否已经过期,具体过程是这样的:当你发送一个GET key命令时,Redis在返回数据之前,会先查看这个键的过期时间,如果发现键已经过期,那么Redis会立即将其删除,然后就像这个键从未存在过一样,向客户端返回一个空值(nil),这种方式的优点是节省CPU资源,因为只在实际被访问的键上执行检查操作,但缺点也很明显:如果一个键永远不再被访问,即使它早已过期,它也会一直占据着内存空间,成为所谓的“僵尸键”,这会造成内存的浪费。

重构Redis时那些键值过期前的细节和机制你知道吗?

为了解决惰性删除可能造成的内存泄漏问题,Redis引入了第二种策略:定期删除(Periodic Expiration),这个策略是主动的、周期性的,Redis会每隔一段时间(默认是每秒10次,可通过hz配置调整)自动执行一次过期键的清理任务,但这个清理过程并不是扫描整个数据库,因为数据库可能非常大,全盘扫描会带来巨大的性能压力。

定期删除的过程要更精巧一些,可以概括为以下几个步骤:

重构Redis时那些键值过期前的细节和机制你知道吗?

  1. 随机抽样:Redis每次会从设置了过期时间的键的集合中,随机抽取一定数量的键(默认是20个)进行检查。
  2. 删除已过期键:对于这20个键,逐一检查它们是否过期,如果过期了,就立即删除。
  3. 判断是否继续:在这20个键中,如果发现有超过一定比例(默认是25%,即5个)的键已经过期,Redis就会认为当前数据库中过期键比较多,于是它会立即再次随机抽取20个键,重复步骤1和2,这个过程会一直循环,直到在某一次抽样中,过期的键比例低于25%,才会停止本轮清理,等待下一次周期到来。

这种自适应的方法很好地在CPU时间和内存释放之间取得了平衡,当过期键不多时,定期删除的任务很轻量;当过期键很多时,它会更“勤奋”地工作,直到将过期键的比例降到阈值以下。

除了这两种核心的删除机制,在Redis的持久化(把数据存到硬盘)过程中,也会涉及到对过期键的处理。

  • 在创建RDB快照时,Redis不会将已经过期的键保存到RDB文件中,当你从一个RDB文件恢复数据时,里面包含的都是未过期的键。
  • 在使用AOF日志时,情况稍有不同,当一个键因为过期而被惰性删除或定期删除时,Redis会向AOF文件末尾追加一条DEL命令,来显式记录这个删除操作,这样,在后续通过AOF文件恢复数据时,即使这个键在原始AOF日志里还存在,执行到后面的DEL命令也会把它删掉,保证了数据的一致性,在Redis重启后,重新加载AOF文件到内存的过程中,它也会主动过滤掉那些已经过期的键。

还有一个容易忽略但很重要的细节,体现在命令的返回值上,有些命令的行为会隐含地对键进行过期检查。DEL命令在成功删除一个键(无论是否过期)后,返回的是整数1,但如果你对一个已经过期的键执行DEL命令,它仍然会返回1,因为从结果上看,这个键确实被移除了,而像EXISTS这样的命令,在判断一个键是否存在时,会先检查其是否过期,如果过期了,EXISTS会返回0(表示不存在),因为它内部触发了惰性删除。

理解这些细节,对于重构Redis应用至关重要,如果你发现内存占用总是比预期的高,可能需要检查是否存在大量从未被访问的过期键,并考虑调整hz参数让定期删除更积极一些,或者,在编写代码时,要明白不能完全依赖EXISTS的结果来判断一个键“曾经”是否存在,因为它可能刚刚因为过期而被惰性删除掉了,这些机制确保了Redis的高效和稳定,但也要求开发者对其内部逻辑有清晰的认知。