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

Redis里那些自动过期的事儿,超时处理怎么搞才靠谱

主要整合自Redis官方文档、经典技术博客如Antirez(Redis作者)的博文、以及《Redis设计与实现》等资料中的常见实践与原理阐述)

Redis里面有一个特别实用的功能,就是可以给存进去的数据设置一个“保质期”,就像超市里的牛奶,过了一定时间就不能要了,这个功能在很多场景下能帮我们大忙,比如手机验证码60秒后失效、用户登录令牌7天后需要重新登录、或者只是为了防止缓存数据无限期堆积占满内存。

那在Redis里,这个“自动过期”是怎么设定的呢?最常用的两个命令是 EXPIRE key seconds 和它的毫秒版 PEXPIRE key milliseconds,你存了一个验证码 SET verification_code 123456,然后立刻跟上 EXPIRE verification_code 60,这样,60秒后,这个键(key)连同它的值(value)就会自动被Redis删除,还有一个更方便的命令叫 SETEX,它能一步到位,在设置值的同时就指定过期时间,SETEX verification_code 60 123456

除了相对时间(从设置的那一刻开始倒计时),Redis还支持设置绝对时间点过期,用的是 EXPIREAT 命令,它需要一个Unix时间戳,比如你希望一个键在今晚12点整失效,就可以计算出那个时间点的时间戳然后设置进去。

数据被设置了过期时间后,我们怎么知道它还能活多久呢?用 TTL key 命令,它返回的是这个键剩余的存活秒数,如果返回-2,意味着这个键已经不存在了(过期被删了或者根本就没这个键);如果返回-1,则说明这个键存在,但压根没设置过期时间,会永久存活,毫秒精度的查看命令是 PTTL

最关键的问题来了:Redis是怎么在后台悄无声息地清理这些过期键的呢?总不能时时刻刻检查每一个键吧?那样CPU就爆了,Redis实际上用了两种策略结合起来做这件事,这是一种在效率和实时性之间的权衡(根据Antirez的阐述和官方文档说明)。

第一种策略叫“被动过期”,顾名思义,它很“懒”,不会主动去扫,只有当某个客户端尝试去访问一个键的时候,Redis才会顺便检查一下这个键是否已经过期,如果过期了,就立刻删除它,然后给客户端返回一个空值,好像这个键从来不存在一样,这个策略的好处是精准打击,只在使用资源的时候才付出检查的成本,但坏处也很明显:如果一个键过期后一直没人访问,它就会一直赖在内存里,成了“僵尸键”,白白占用空间。

为了解决“僵尸键”的问题,Redis启动了第二种策略:“主动过期”,这是一个后台任务,会定期运行,它也不是傻乎乎地遍历整个数据库,那样数据量大的时候会卡死服务,Redis的主动过期策略是自适应、非精确的清理,它每次会随机抽取一批设置了过期时间的键进行检查(Antirez在博客中解释过这种随机采样的重要性),删除其中已经过期的,它会判断一下这批键里过期键的比例,如果比例很高,说明内存里过期的“垃圾”很多,它就立刻再抽一批继续清理,直到过期键的比例降到一个可控的水平它才休息,这个过程会周期性地重复,确保内存能得到基本的回收。

你看,Redis的过期清理是“被动检查 + 主动定期抽样清理”双管齐下,这也就意味着,过期键的删除并不是在过期时间到达的那一刻准时发生的,它可能会有一个小小的延迟,尤其是在主动清理的间隔期内,如果也没有读请求来触发被动删除,那么这个过期的键就会多存活一小段时间,对于绝大多数应用场景,这点延迟是可以接受的,但如果你有非常严格的实时性要求(比如金融交易中超时订单的释放),就需要意识到这个机制可能带来的微小延迟。

那我们在用的时候,怎么搞才更靠谱呢?有几点可以参考:

  1. 别把Redis当数据库,它是个缓存:首先要明确,虽然Redis有持久化功能,但过期删除机制主要是为缓存场景设计的,不要把有强一致性要求的、绝对不能丢的数据只依赖Redis的过期功能来管理,过期更像是一种资源清理和逻辑失效的手段,而不是一个绝对可靠的数据生命周期管理器。

  2. 监控是关键:要关注Redis的内存使用情况used_memory和过期键的数量expired_keys,如果发现过期键的数量增长很快但内存释放不明显,可能意味着主动清理策略跟不上键过期的速度,或者有很多“僵尸键”,这时候可能需要调整Redis的主动清理频率相关配置(hz参数需谨慎调整),或者检查业务代码是否有大量设置短时间过期却不再访问的键。

  3. 考虑使用其他数据结构辅助:对于批量过期或者有严格时间顺序要求的场景,可以结合使用有序集合(Sorted Set),比如管理会话,可以把会话ID作为成员,把过期时间戳作为分数存入有序集合,然后另起一个后台任务,定期用 ZRANGEBYSCORE 命令查询当前时间戳之前的所有成员,这些就是该过期的会话,可以批量取出并做清理,这样能实现更精确的控制,但代价是增加了实现的复杂性。

  4. 警惕内存淘汰策略的副作用:当Redis内存用完时,会根据配置的maxmemory-policy来决定怎么办,如果策略是volatile-lru之类的,它会尝试淘汰那些设置了过期时间的键来腾空间,如果你的应用同时使用了永不过期的键和设置了过期的键,要小心在内存压力下,重要的、设置了过期的缓存被提前淘汰,而一些不那么重要但永久的键却留了下来,根据业务特点选择合适的淘汰策略很重要。

Redis的自动过期是个强大的工具,但理解其“非精确”和“延迟清理”的内在机制是可靠使用它的前提,把它用在合适的场景(缓存、会话、临时锁等),配合适当的监控,就能让它既帮你省心省力,又不会在关键时刻掉链子。

Redis里那些自动过期的事儿,超时处理怎么搞才靠谱