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

Redis里头怎么配数据过期,顺便聊聊那些用过期功能的场景和坑

(主要参考来源:Redis官方文档关于键过期的说明,以及《Redis实战》中关于过期时间的讨论)

Redis里头设置数据过期特别简单,主要就靠两个命令:EXPIRE 和它的亲戚们,你比如说,EXPIRE key 60 就是告诉Redis,这个键60秒后自个儿失效,如果你想用分钟、小时甚至天数来算,有 PEXPIRE(毫秒)、EXPIREAT(指定具体过期时间戳)、PEXPIREAT(指定毫秒级时间戳),反正花样挺多,但干的事儿都一样:给数据上个闹钟。

设置过期时间还有更省事儿的办法,就是在存数据的时候直接带上,比如用 SET key value EX 60,一步到位,键值对和过期时间都搞定了,这比先SETEXPIRE省了一次网络通信,在高并发场景下能提升点性能。

(参考来源:Redis Labs博客关于内存优化的案例) 那为啥要用这个功能呢?最常见的场景就是缓存。 这是过期功能的老本行,比如你网站首页的热门文章列表,从数据库里查出来费劲,就扔Redis里,设个5分钟过期,这样5分钟内所有用户都直接从Redis里拿数据,数据库压力小多了,时间一到,数据自动删除,下次有人访问发现缓存没了,再去数据库查新的出来塞回去,这样既保证了速度,又不会让数据一直占着茅坑不拉屎。

第二个大场景是限时活动。 比如电商平台的限时秒杀优惠券,你领了券,这个券信息存Redis里,有效期就设成活动结束的时间,时间一过,券自动作废,系统根本不用额外写个定时任务去扫描清理,省心又不容易出错,还有像用户登录后的会话信息(Session),通常也会设置一个过期时间(比如30分钟),用户如果30分钟没操作,Session自动失效,要求重新登录,这既安全又帮我们自动清理了不活跃的用户数据。

Redis里头怎么配数据过期,顺便聊聊那些用过期功能的场景和坑

第三个是临时性的锁或者信号量。 比如防止重复提交,用户提交一个订单后,立马往Redis里塞一个键,设置10秒过期,在这10秒内,同一个用户再提交,系统一查Redis这个键还在,就认为是重复提交,给拒了,10秒后锁自动释放,允许用户再次操作,这里有个关键点,一定要设过期时间,不然万一系统在处理完业务后忘了删这个锁,那就成死锁了,用户永远没法再提交。

用过期功能也不是高枕无忧,坑也不少。

第一个大坑:过期删除策略带来的延迟问题。 Redis并不是键一到时间就立刻删除的,那样太耗CPU了,它用的是惰性删除加定期删除的混合策略,惰性删除就是等客户端来访问这个键的时候,Redis才检查它过没过期,过期了就删了然后返回空,定期删除是Redis每隔一段时间随机抽查一些键,把过期的清理掉,这就可能出问题:如果一个键已经过期了,但一直没被访问,Redis又还没随机抽到它,那这个键就会在内存里多呆一阵子,白占着空间,如果这种过期键太多,可能会导致实际内存占用比预期的高,极端情况下,内存满了,还会触发Redis的内存淘汰机制(比如LRU),可能误伤一些没过期但最近不常用的键。

Redis里头怎么配数据过期,顺便聊聊那些用过期功能的场景和坑

第二个坑:主从复制下的过期行为。 在Redis主从架构里,过期键是由主节点来负责删除的,主节点删除一个过期键后,会向从节点发一个 DEL 命令,但这里有个时序问题:如果主节点因为网络延迟或者压力大,没有及时发出DEL命令,那么从节点上这个“过期”键就会多存在一会儿,客户端如果这时候去读从节点,就会读到本该已经过期的脏数据,对数据一致性要求极高的场景,读从库时要心里有数。

第三个坑:DEL命令和过期时间的混淆。 如果你手动用一个DEL命令把键删了,那这个键的过期时间自然也就跟着一起没了,但反过来,如果一个键是因为过期而被Redis自动删除的,这个删除操作会被当做一个事件,如果能触发Redis的键空间通知(keyspace notification),你可以订阅这个通知来干些后续工作,比如记录日志或者触发更新,但默认这功能是关着的,需要你主动配置开启。

第四个是持久化时的坑。 Redis有两种持久化方式:RDB和AOF,生成RDB快照时,过期的键不会被保存进去,这没问题,但用AOF日志时,当一个键过期,Redis会追记一条DEL命令到AOF文件里,问题在于,如果你重启Redis,需要重放AOF文件来恢复数据,这个DEL命令会在重放时执行,但如果你的系统时间在重启后发生了混乱(比如被调回了过去),可能导致重放时判断这个键还没到期,但DEL命令又执行了,造成数据不一致,所以保证服务器时间准确、稳定非常重要。

Redis的过期功能是个强大的工具,用好了能简化很多设计,比如缓存、会话管理、限流等等,但你必须清楚它背后的机制不是实时的,存在一定的延迟和不确定性,特别是在分布式和持久化场景下,要考虑到这些边界情况,避免它们成为线上故障的隐患。