适用版本: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 memory、MEMORY 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-lru或allkeys-lfu - 必须显式控制 TTL 的场景:
volatile-lru或volatile-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表示 2GBallkeys-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 memory、mem_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 的过期键与内存淘汰,可以抓住下面几个核心点:
- 过期键不等于到点立删,Redis 通过惰性删除与定期删除配合处理
- 内存淘汰发生在达到
maxmemory之后,用于决定“内存不够时删谁” - TTL 配置、淘汰策略、业务访问模式三者必须配套设计
- 缓存场景中,合理设置 TTL 与淘汰策略,往往比单纯扩大内存更重要
如果你能把这两套机制真正理解透,Redis 在缓存治理、内存控制、性能排查上的很多问题都会更容易分析。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!