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

Redis里怎么给数据加有效期,存储过期信息的那些事儿

在Redis中,给数据设置有效期是一个非常常用且核心的功能,它让我们可以轻松管理数据的生命周期,比如实现缓存、验证码过期、限时活动等场景,这事儿说起来简单,但背后Redis是怎么处理和存储这些过期信息的,却有不少门道。

如何给数据设置有效期?

根据Redis官方文档和普遍的使用方法,主要有以下几种方式给数据加“保质期”:

  1. 在设置键值对的同时指定过期时间

    • SET key value EX seconds:这是最常用的命令。SET login:token "user123" EX 300,意思是将键 login:token 的值设置为 “user123”,并且它在300秒(5分钟)后会自动过期,这里的 EX 单位是秒。
    • SET key value PX milliseconds:这个命令和上面的类似,但过期时间是以毫秒为单位指定的,精度更高。SET captcha:1234 "ABCD" PX 60000 表示验证码在60秒后过期。
    • SET key value EXAT timestamp:这个命令允许你指定一个精确的Unix时间戳(秒级),表示键将在哪个具体的时间点过期。SET event:start "begin" EXAT 1672502400 表示该键在2023年1月1日00:00:00 UTC过期。
    • SET key value PXAT timestamp:和 EXAT 类似,但指定的是毫秒级的时间戳。
  2. 对已存在的键单独设置过期时间 有时候我们可能先创建了一个键,后来才需要给它加上过期时间,这时可以用以下命令:

    Redis里怎么给数据加有效期,存储过期信息的那些事儿

    • EXPIRE key seconds:为已存在的键 key 设置在 seconds 秒后过期。
    • PEXPIRE key milliseconds:以毫秒为单位设置过期时间。
    • EXPIREAT key timestamp:为键设置一个过期的绝对时间点(秒级时间戳)。
    • PEXPIREAT key timestamp:为键设置一个过期的绝对时间点(毫秒级时间戳)。
  3. 查看和移除过期时间

    • TTL key:查看键剩余的存活时间,单位是秒,如果返回 -2,表示键不存在;返回 -1,表示键存在但没有设置过期时间。
    • PTTL key:和 TTL 类似,但返回的是毫秒。
    • PERSIST key:如果一个键设置了过期时间,使用这个命令可以移除它的过期时间,使其成为一个永久的键。

Redis是怎么存储和管理这些过期信息的?(存储过期信息的那些事儿)

这部分是重点,也是大家常说的“背后的故事”,Redis并不是为每个键值对额外存储一个“过期时间”字段那么简单,它采用了两种主要的策略来高效地管理海量键的过期,这两种策略是结合使用的。

Redis里怎么给数据加有效期,存储过期信息的那些事儿

  1. 过期字典(Expires Dictionary) 根据对Redis源码的分析(如知乎用户“程序员小灰”等人的解读),Redis内部维护了一个叫做“过期字典”的特殊数据结构,你可以把它想象成一个哈希表,这个字典的“键”指向Redis数据库中那些设置了过期时间的键,而字典的“值”则保存了该键的过期时间点(一个毫秒精度的Unix时间戳)。 当我们使用 EXPIREPEXPIREAT 等命令时,Redis实际上就是在向这个过期字典里插入或更新一条记录,当需要检查一个键是否过期时,Redis会先到这个字典里查一下,如果找不到记录,说明这个键是永久的;如果找到了,就把当前时间戳和字典里存储的过期时间戳进行比较,判断是否过期。

  2. 过期键的删除策略 光知道哪个键什么时候过期还不够,关键是要在它过期后把它清理掉,释放内存,Redis采用了两种删除策略的结合:

    • 惰性删除(Lazy Expiration):这种策略很简单,用到的时候才检查”,当客户端尝试访问一个键时,Redis会先去检查它的过期时间(查询上述的过期字典),如果发现键已经过期,就立刻删除它,然后返回空值,就像这个键不存在一样,这种策略的优点是节省CPU,因为只在实际访问时才会付出检查的代价,缺点是如果过期键永远不被访问,那么即使它已经过期,也会一直占用着内存,造成内存泄漏。
    • 定期删除(Periodic Expiration):为了解决惰性删除可能造成的内存泄漏问题,Redis会定期地主动检查和删除一批过期键,这个过程不是扫描整个数据库,而是抽样进行,Redis会每隔一段时间(默认配置是每秒10次,即100ms一次)随机从设置了过期时间的键中抽取一小部分(比如20个键),检查它们是否过期,如果过期的比例很高,说明当前数据库中有很多键已经过期,Redis会再次抽取更多的样本进行检查和删除,直到过期的比例降下来,这种策略是惰性删除的一个补充,在CPU和内存之间取得了一个平衡。

一些需要注意的细节和“坑”

了解了基本原理后,在实际使用中还有一些细节需要注意,这些也是社区讨论中经常提到的问题:

  • 过期事件的触发:Redis的键过期是“被动”的,意思是,一个键到期后,Redis不会主动发送通知告诉客户端“这个键过期了”,只有当客户端尝试去获取这个键时,才会触发惰性删除并发现它已过期,虽然Redis有发布订阅功能可以模拟键空间通知,但那需要额外配置和订阅,并且可靠性需要自己保证。
  • 主从复制下的过期:在Redis主从架构中,过期键的删除操作是由主节点(Master)控制的,主节点在删除一个过期键后,会向所有从节点(Slave)发送一个 DEL 命令,从节点自身不会主动删除过期键,它们只会被动接收主节点的指令,这是为了保持数据的一致性,由主节点统一决策。
  • 持久化时的处理:当Redis进行数据持久化(RDB快照或AOF日志)时,也会处理过期键,生成RDB文件时,过期的键不会被保存到快照中,在载入RDB文件时,如果是主服务器模式,会检查键是否过期;如果是从服务器模式,则会全部载入,等待主节点同步DEL命令,AOF日志在重写(Rewrite)时,也会像生成RDB一样,忽略那些已经过期的键。

Redis通过过期字典精确记录每个键的“死期”,再结合惰性删除和定期删除这两种互补的策略,巧妙地平衡了性能和内存使用效率,使得处理海量数据的过期变得高效而实用,理解这些机制,能帮助我们在使用Redis设置过期时间时更加得心应手,避免踩坑。