返回首页

Redis核心2.2: 过期键与内存淘汰机制

适用版本:Redis 7.2.x

在很多人的第一印象里,Redis 就是“把数据放进内存里,然后读写特别快”。但当数据越来越多时,问题很快就会出现:

  • 有些键只想保留一段时间,过期后要自动删除
  • 内存不是无限的,达到上限之后该怎么办
  • 为什么明明设置了 10 秒过期,键却不是 10 秒整立刻消失

这些问题都指向 Redis 的两个核心机制:过期键内存淘汰

前者解决的是“键什么时候失效”,后者解决的是“内存不够时该删谁”。两者经常一起出现在生产环境中,也是排查缓存异常、命中率下降、内存占满时必须掌握的知识点。


一、什么是过期键

过期键,指的是为某个 key 设置了生存时间(TTL,Time To Live)的键。到达过期时间后,这个键应当被 Redis 删除。

常见场景:

  • 登录验证码 5 分钟后失效
  • 用户会话 30 分钟未续期则过期
  • 热点缓存 1 小时后自动刷新
  • 分布式锁设置短 TTL 防止死锁

从使用角度看,过期键的价值非常大:

  • 避免业务手工清理无效数据
  • 控制缓存生命周期
  • 降低内存长期堆积的风险

二、Redis 如何设置过期时间

1. 常用命令

Redis 提供了多种设置过期时间的命令:

SET session:user:1001 "token-value"
EXPIRE session:user:1001 1800

SET code:sms:user:1001 "9527" EX 300

PEXPIRE session:user:1001 1800000
TTL session:user:1001
PTTL session:user:1001
PERSIST session:user:1001

这些命令的含义如下:

  • EXPIRE key seconds:按秒设置过期时间
  • PEXPIRE key milliseconds:按毫秒设置过期时间
  • SET key value EX seconds:写入值时同时设置过期时间
  • TTL key:查看剩余生存时间(秒)
  • PTTL key:查看剩余生存时间(毫秒)
  • PERSIST key:移除过期时间,让键重新变成永久键

2. 一个简单示例

127.0.0.1:6379> SET article:1 "hello" EX 10
OK

127.0.0.1:6379> TTL article:1
(integer) 8

127.0.0.1:6379> GET article:1
"hello"

# 若过一段时间后再次读取
127.0.0.1:6379> GET article:1
(nil)

这表示 article:1 在 10 秒后过期,过期后读取不到值。


三、过期键真的会在到点那一刻立刻删除吗?

这是初学 Redis 时最常见的误区之一。

答案是:不一定

Redis 并不是给每个键都挂一个定时器,到点就立刻删除。那样做在海量键场景下成本太高。Redis 实际采用的是两种策略结合:

  • 惰性删除
  • 定期删除

1. 惰性删除

惰性删除的意思是:

某个键已经过期了,但 Redis 不会主动马上删掉它;只有当客户端再次访问这个键时,Redis 才发现它已经过期,然后执行删除。

优点:

  • 对 CPU 更友好
  • 不需要维护海量定时任务

缺点:

  • 如果过期键迟迟没有被访问,就会继续占用内存

2. 定期删除

为了避免大量过期键长期占内存,Redis 还会周期性地随机抽样带过期时间的键,检查并清理其中已过期的部分。

优点:

  • 能主动回收一部分已经过期的键
  • 减轻惰性删除导致的内存滞留问题

缺点:

  • 它是抽样删除,不是全量扫描
  • 过期键仍然可能在短时间内残留

3. 结论

因此,设置了过期时间并不意味着:

  • 键会在到点瞬间 100% 立即消失
  • 内存会在同一时刻立刻释放干净

更准确的理解应该是:

Redis 会尽可能高效地让过期键失效,并在访问或周期扫描时完成清理。


四、与过期键相关的几个重要细节

1. 覆盖写入可能影响 TTL

SET user:1:name "Alice"
EXPIRE user:1:name 60
SET user:1:name "Bob"
TTL user:1:name

很多情况下,重新 SET 一个键会让原来的 TTL 丢失,键重新变成永久键。因此业务代码里如果需要“更新值但保留 TTL”,需要额外留意写法。

2. 重命名键时 TTL 会跟着走

使用 RENAME 时,过期时间会跟随键一起移动。这一点在做临时数据迁移时容易被忽略。

3. 删除过期键并不等于立即归还全部内存

一些复杂对象、大对象,或者涉及后台异步释放时,内存释放的体感可能不是瞬时的。因此排查内存问题时,不要只盯着 TTL,也要结合 INFO memoryMEMORY STATS 一起看。


五、什么是内存淘汰

当 Redis 配置了 maxmemory,并且实际使用内存达到上限后,再有新的写入请求时,Redis 就要决定:

  • 是报错拒绝写入
  • 还是删掉一部分旧数据,腾出空间

这套策略就是 内存淘汰机制

可以把它理解为:

过期键解决“这个数据什么时候该失效”,内存淘汰解决“内存满了先牺牲谁”。

这两者相关,但不是同一件事。


六、maxmemory 与淘汰策略配置

1. 常见配置项

maxmemory 4gb
maxmemory-policy allkeys-lru

其中:

  • maxmemory:Redis 可使用的最大内存
  • maxmemory-policy:达到上限后的淘汰策略

查看当前配置:

CONFIG GET maxmemory
CONFIG GET maxmemory-policy
INFO memory

2. 常见淘汰策略

Redis 常见的淘汰策略如下:

策略 含义
noeviction 不淘汰,内存满后写命令直接报错
allkeys-lru 在所有键中,淘汰最近最少使用的键
volatile-lru 只在设置了过期时间的键中,淘汰最近最少使用的键
allkeys-lfu 在所有键中,淘汰最不经常使用的键
volatile-lfu 只在带过期时间的键中,淘汰最不经常使用的键
allkeys-random 在所有键中随机淘汰
volatile-random 只在带过期时间的键中随机淘汰
volatile-ttl 优先淘汰剩余 TTL 更短的键

在 Redis 7.2.x 环境中,常见实践通常是:

  • 缓存场景:allkeys-lruallkeys-lfu
  • 必须显式控制 TTL 的场景:volatile-lruvolatile-ttl
  • 不允许自动丢数据的场景:noeviction

3. LRU 和 LFU 怎么理解

  • LRU(Least Recently Used):最近最少使用,谁最近最少被访问,谁更可能被淘汰
  • LFU(Least Frequently Used):最不经常使用,谁访问频率更低,谁更可能被淘汰

简单理解:

  • LRU 更关注“最近热不热”
  • LFU 更关注“长期是否热门”

七、配置与命令示例

1. 给缓存键统一设置过期时间

SET cache:product:1001 "{...}" EX 600
SET cache:product:1002 "{...}" EX 600
SET cache:product:1003 "{...}" EX 600

这种方式适合缓存类数据,能避免缓存永久堆积。

2. 设置最大内存和淘汰策略

CONFIG SET maxmemory 2147483648
CONFIG SET maxmemory-policy allkeys-lfu

说明:

  • 2147483648 表示 2GB
  • allkeys-lfu 表示在所有键中按 LFU 淘汰

3. 查看热点与内存状态

INFO memory
INFO stats
MEMORY STATS
MEMORY USAGE cache:product:1001

这些命令常用于排查:

  • 内存是否逼近上限
  • 是否发生淘汰
  • 某个大 key 是否占用异常

八、常见问题

1. 为什么我设置了 EXPIRE 10,10 秒后键还在?

因为 Redis 的过期删除不是“逐键定时器模式”,而是通过惰性删除和定期删除配合完成。键可能在访问时才被发现过期,也可能在后续周期扫描中才被清理。

2. 为什么内存满了却没有淘汰任何键?

常见原因有:

  • 配置的是 noeviction
  • 配置的是 volatile-* 策略,但当前几乎没有带 TTL 的键
  • 写入压力过大,淘汰跟不上增长速度

这类问题在线上非常常见,特别是使用 volatile-lru 时,如果业务忘记给键设置过期时间,Redis 就几乎没有可淘汰对象。

3. 过期键删除后,内存为什么没有明显下降?

可能原因包括:

  • 删除是逐步完成的,不是瞬时全清
  • 内存碎片导致观测值不敏感
  • 大对象释放存在后台行为
  • 实例同时又写入了新数据

排查时要结合 INFO memorymem_fragmentation_ratio、大 key 分析一起看。

4. LRU 和 LFU 应该怎么选?

如果你的访问热度变化很快,LRU 往往比较直观;如果你的数据冷热分层明显,希望更稳定保留高频热键,LFU 通常更合适。


九、实践建议

1. 缓存键尽量都设置 TTL

如果 Redis 主要作为缓存使用,一个很稳妥的实践是:

  • 默认所有缓存键都设置过期时间
  • 不要让大量缓存永久存在

这样做既有利于数据自动淘汰,也能让 volatile-* 策略真正生效。

2. TTL 建议加入随机值,避免雪崩

不要让大量热点键在同一秒一起过期。更稳妥的写法是:

# 基础 TTL 600 秒,再加 0~120 秒随机偏移
SET cache:feed:user:1001 "..." EX 673

这样可以降低缓存雪崩风险。

3. 不要把 maxmemory 设到机器物理内存上限

Redis 是内存型服务,但系统还需要预留空间给:

  • 操作系统
  • fork 时的额外开销
  • 网络缓冲区
  • 持久化时的 Copy-On-Write

实践中通常要留出足够余量,而不是把机器内存全部压满。

4. 先选策略,再匹配业务写法

比如你选择了 volatile-lru,那就必须保证业务中的缓存键普遍带 TTL;否则策略本身就失去意义。

5. 关注淘汰指标,而不是只看内存大小

很多线上问题不是“Redis 内存高”,而是“Redis 一直在高频淘汰,导致命中率下降、后端数据库压力暴涨”。因此要关注:

  • evicted_keys
  • 命中率变化
  • 大 key 分布
  • 热点键访问模式

十、小结

学习 Redis 的过期键与内存淘汰,可以抓住下面几个核心点:

  1. 过期键不等于到点立删,Redis 通过惰性删除与定期删除配合处理
  2. 内存淘汰发生在达到 maxmemory 之后,用于决定“内存不够时删谁”
  3. TTL 配置、淘汰策略、业务访问模式三者必须配套设计
  4. 缓存场景中,合理设置 TTL 与淘汰策略,往往比单纯扩大内存更重要

如果你能把这两套机制真正理解透,Redis 在缓存治理、内存控制、性能排查上的很多问题都会更容易分析。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

Redis核心2.1:RDB 与 AOF持久化详解

下一篇

Redis核心2.3: 发布订阅机制入门与实践